Skip to content

Commit

Permalink
Merge branch 'main' into jgmw/ci-jobs-e2e
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh-Walker-GM committed Aug 23, 2024
2 parents ef1ae74 + 974a245 commit 75d9547
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 133 deletions.
179 changes: 179 additions & 0 deletions packages/cli/src/__tests__/Listr2Mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import type Enquirer from 'enquirer'
import type * as Listr from 'listr2'

type Ctx = Record<string, any>

type TListrTask = Listr.ListrTask<Ctx, typeof Listr.ListrRenderer>
type EnquirerPromptOptions = Parameters<Enquirer['prompt']>[0]
type Function = { length: number; name: string }
type PlainPromptOptions = ReturnType<Extract<EnquirerPromptOptions, Function>>
type ListrPromptOptions = Parameters<
Listr.ListrTaskWrapper<Ctx, typeof Listr.ListrRenderer>['prompt']
>[0]

function isNotFunctionPromptOptions(
opts: EnquirerPromptOptions,
): opts is PlainPromptOptions | PlainPromptOptions[] {
return (
typeof opts !== 'function' &&
(Array.isArray(opts) ? opts.every((o) => typeof o !== 'function') : true)
)
}

class Listr2TaskWrapper {
task: Listr.ListrTaskObject<Ctx, typeof Listr.ListrRenderer>
promptOutput: string
prompt: <T = any>(options: ListrPromptOptions) => Promise<T>
skip: (msg: string) => void

// This is part of Listr.TaskWrapper, but we don't need it
// private options: Record<PropertyKey, any> | undefined

constructor({
task,
prompt,
skip,
// options,
}: {
task: Listr.ListrTaskObject<Ctx, typeof Listr.ListrRenderer>
prompt: <T = any>(options: ListrPromptOptions) => Promise<T>
skip: (msg: string) => void
options?: Record<PropertyKey, any> | undefined
}) {
this.task = task
this.prompt = prompt
this.skip = skip
// this.options = options

this.promptOutput = ''
}

async run() {}
report() {}
cancelPrompt() {}
stdout() {
return process.stdout
}

get title(): string | any[] | undefined {
return this.task.title
}
set title(title: string) {
this.task.title = title
}

get output(): string | undefined {
return this.task.output
}

newListr(tasks: TListrTask[], options?: Listr.ListrOptions) {
return new Listr2Mock(tasks, options)
}

isRetrying() {
return false
}
}

export class Listr2Mock {
static executedTaskTitles: string[]
static skippedTaskTitles: string[]

ctx: Ctx
tasks: TListrTask[]
listrOptions?: Listr.ListrOptions | undefined

constructor(
tasks: TListrTask[],
listrOptions?: Listr.ListrOptions | undefined,
) {
this.ctx = {}
this.tasks = tasks
this.listrOptions = listrOptions
}

async run() {
Listr2Mock.executedTaskTitles = []
Listr2Mock.skippedTaskTitles = []

for (const task of this.tasks) {
const skip = typeof task.skip === 'function' ? task.skip : () => task.skip

const skipReturnValue = skip(this.ctx)

if (typeof skipReturnValue === 'string') {
// skip() => 'message'
Listr2Mock.skippedTaskTitles.push(skipReturnValue)
continue
} else if (skipReturnValue) {
// skip() => true
const taskTitle = typeof task.title === 'string' ? task.title : ''
Listr2Mock.skippedTaskTitles.push(taskTitle)
continue
}

const augmentedTask = new Listr2TaskWrapper({
// @ts-expect-error - TODO: Fix the types here
task: task.task,
prompt: async <T = any>(options: ListrPromptOptions) => {
const enquirer = this.listrOptions?.injectWrapper?.enquirer as
| Enquirer<T extends object ? T : never>
| undefined

if (!enquirer) {
throw new Error('Enquirer instance not available')
}

// TODO: Fix the types here
if (!isNotFunctionPromptOptions(options as EnquirerPromptOptions)) {
throw new Error(
'Function prompt options are not supported by the mock',
)
}

const enquirerOptions = !Array.isArray(options)
? [{ ...options, name: 'default' }]
: options

if (enquirerOptions.length === 1) {
enquirerOptions[0].name = 'default'
}

const response = await enquirer.prompt(
// @ts-expect-error - the type should be EnquirerPromptOptions
enquirerOptions,
)

if (enquirerOptions.length === 1 && 'default' in response) {
return response.default as T
}

return response
},
skip: (msg: string) => {
const taskTitle = typeof task.title === 'string' ? task.title : ''
Listr2Mock.skippedTaskTitles.push(msg || taskTitle)
},
})

await task.task(
this.ctx,
// TODO: fix this by removing the type casts.
// The reason we have to do this is because of private fields in
// our own Listr2TaskWrapper and Listr.ListrTaskWrapper
augmentedTask as unknown as Listr.ListrTaskWrapper<
Ctx,
typeof Listr.ListrRenderer
>,
)

// storing the title after running the task in case the task
// modifies its own title
if (typeof augmentedTask.title === 'string') {
Listr2Mock.executedTaskTitles.push(augmentedTask.title)
} else if (typeof task.title === 'string') {
Listr2Mock.executedTaskTitles.push(task.title)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
let mockExecutedTaskTitles = []
let mockSkippedTaskTitles = []

global.__dirname = __dirname

vi.mock('fs-extra')
Expand All @@ -23,65 +20,13 @@ import {
afterAll,
} from 'vitest'

import { Listr2Mock } from '../../../../__tests__/Listr2Mock'
import { getPaths } from '../../../../lib'
import * as dbAuth from '../dbAuth'

vi.mock('listr2', async () => {
const ctx = {}
const listrImpl = (tasks, listrOptions) => {
return {
ctx,
run: async () => {
mockExecutedTaskTitles = []
mockSkippedTaskTitles = []

for (const task of tasks) {
const skip =
typeof task.skip === 'function' ? task.skip : () => task.skip

if (skip()) {
mockSkippedTaskTitles.push(task.title)
} else {
const augmentedTask = {
...task,
newListr: listrImpl,
prompt: async (options) => {
const enquirer = listrOptions?.injectWrapper?.enquirer

if (enquirer) {
if (!Array.isArray(options)) {
options = [{ ...options, name: 'default' }]
} else if (options.length === 1) {
options[0].name = 'default'
}

const response = await enquirer.prompt(options)

if (options.length === 1) {
return response.default
}
}
},
skip: (msg) => {
mockSkippedTaskTitles.push(msg || task.title)
},
}
await task.task(ctx, augmentedTask)

// storing the title after running the task in case the task
// modifies its own title
mockExecutedTaskTitles.push(augmentedTask.title)
}
}
},
}
}

return {
// Return a constructor function, since we're calling `new` on Listr
Listr: vi.fn().mockImplementation(listrImpl),
}
})
vi.mock('listr2', () => ({
Listr: Listr2Mock,
}))

// Mock files needed for each test
const mockFiles = {}
Expand Down Expand Up @@ -159,7 +104,7 @@ describe('dbAuth handler WebAuthn task title', () => {
listr2: { silentRendererCondition: true },
})

expect(mockExecutedTaskTitles[1]).toEqual(
expect(Listr2Mock.executedTaskTitles[1]).toEqual(
'Querying WebAuthn addition: WebAuthn addition included',
)
})
Expand Down Expand Up @@ -193,7 +138,7 @@ export const { AuthProvider, useAuth } = createAuth(dbAuthClient)
passwordLabel: 'password',
})

expect(mockSkippedTaskTitles[1]).toEqual(
expect(Listr2Mock.skippedTaskTitles[1]).toEqual(
'Querying WebAuthn addition: WebAuthn setup detected - ' +
'support will be included in pages',
)
Expand Down Expand Up @@ -227,7 +172,7 @@ export const { AuthProvider, useAuth } = createAuth(dbAuthClient)
passwordLabel: 'password',
})

expect(mockSkippedTaskTitles[1]).toEqual(
expect(Listr2Mock.skippedTaskTitles[1]).toEqual(
'Querying WebAuthn addition: No WebAuthn setup detected - ' +
'support will not be included in pages',
)
Expand All @@ -250,7 +195,7 @@ export const { AuthProvider, useAuth } = createAuth(dbAuthClient)
listr2: { silentRendererCondition: true },
})

expect(mockExecutedTaskTitles[1]).toEqual(
expect(Listr2Mock.executedTaskTitles[1]).toEqual(
'Querying WebAuthn addition: WebAuthn addition not included',
)
})
Expand All @@ -267,7 +212,7 @@ export const { AuthProvider, useAuth } = createAuth(dbAuthClient)
webauthn: true,
})

expect(mockSkippedTaskTitles[0]).toEqual(
expect(Listr2Mock.skippedTaskTitles[0]).toEqual(
'Querying WebAuthn addition: argument webauthn passed, WebAuthn included',
)
})
Expand All @@ -284,7 +229,7 @@ export const { AuthProvider, useAuth } = createAuth(dbAuthClient)
webauthn: false,
})

expect(mockSkippedTaskTitles[0]).toEqual(
expect(Listr2Mock.skippedTaskTitles[0]).toEqual(
'Querying WebAuthn addition: argument webauthn passed, WebAuthn not included',
)
})
Expand Down
Loading

0 comments on commit 75d9547

Please sign in to comment.