Skip to content

Commit

Permalink
test: convert config.test.ts to ESM
Browse files Browse the repository at this point in the history
  • Loading branch information
rossiam committed Jan 24, 2024
1 parent e03a654 commit b0d070c
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 52 deletions.
6 changes: 2 additions & 4 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import { defaultsESM as tsjPreset } from 'ts-jest/presets'

const config: JestConfigWithTsJest = {
testMatch: [
// TODO: put this back when all unit tests are converted
// '**/__tests__/**/*.test.ts',
'**/__tests__/lib/**/*.test.ts',
'**/__tests__/**/*.test.ts',
],
setupFilesAfterEnv: ['jest-extended/all'],
collectCoverageFrom: ['src/**/*.ts'],
Expand All @@ -25,7 +23,7 @@ const config: JestConfigWithTsJest = {
reporters: [
'default',
'github-actions',
'jest-html-reporters',
['jest-html-reporters', { darkTheme: true }],
],
extensionsToTreatAsEsm: ['.ts'],
// moduleNameMapper and transform are for ES module support.
Expand Down
231 changes: 183 additions & 48 deletions src/__tests__/commands/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,99 @@
import { ArgumentsCamelCase } from 'yargs'
import { jest } from '@jest/globals'

import { Profile, ProfilesByName } from '../../lib/cli-config'
import cmd, { CommandArgs, ProfileWithName } from '../../commands/config.js'
import { outputItem, outputList } from '../../lib/command/basic-io.js'
import { ArgumentsCamelCase, Argv } from 'yargs'

import { Profile, ProfilesByName } from '../../lib/cli-config.js'
import { CommandArgs } from '../../commands/config.js'
import { outputItem, outputList, outputListBuilder } from '../../lib/command/basic-io.js'
import { stringTranslateToId } from '../../lib/command/command-util.js'
import { calculateOutputFormat, writeOutput } from '../../lib/command/output'
import { TableCommonListOutputProducer, TableCommonOutputProducer } from '../../lib/command/format.js'
import { outputItemOrListBuilder } from '../../lib/command/listing-io.js'
import {
OutputFormatter,
calculateOutputFormat,
writeOutput,
} from '../../lib/command/output.js'
import { buildOutputFormatter } from '../../lib/command/output-builder.js'
import { SmartThingsCommand, SmartThingsCommandFlags, smartThingsCommand } from '../../lib/command/smartthings-command'
import {
SmartThingsCommand,
SmartThingsCommandFlags,
smartThingsCommand,
smartThingsCommandBuilder,
} from '../../lib/command/smartthings-command.js'
import { ValueTableFieldDefinition } from '../../lib/table-generator.js'


const outputItemMock: jest.Mock<typeof outputItem> = jest.fn()
const outputListMock: jest.Mock<typeof outputList> = jest.fn()
jest.unstable_mockModule('../../lib/command/basic-io.js', () => ({
outputListBuilder,
outputItem: outputItemMock,
outputList: outputListMock,
}))

const stringTranslateToIdMock: jest.Mock<typeof stringTranslateToId> = jest.fn()
jest.unstable_mockModule('../../lib/command/command-util.js', () => ({
stringTranslateToId: stringTranslateToIdMock,
}))

const outputItemOrListBuilderMock: jest.Mock<typeof outputItemOrListBuilder> = jest.fn()
jest.unstable_mockModule('../../lib/command/listing-io.js', () => ({
outputItemOrListBuilder: outputItemOrListBuilderMock,
}))

const calculateOutputFormatMock: jest.Mock<typeof calculateOutputFormat> = jest.fn()
const writeOutputMock: jest.Mock<typeof writeOutput> = jest.fn()
jest.unstable_mockModule('../../lib/command/output.js', () => ({
calculateOutputFormat: calculateOutputFormatMock,
writeOutput: writeOutputMock,
}))

const buildOutputFormatterMock: jest.Mock<typeof buildOutputFormatter> = jest.fn()
jest.unstable_mockModule('../../lib/command/output-builder.js', () => ({
buildOutputFormatter: buildOutputFormatterMock,
}))

const smartThingsCommandMock: jest.Mock<typeof smartThingsCommand> = jest.fn()
const smartThingsCommandBuilderMock: jest.Mock<typeof smartThingsCommandBuilder> = jest.fn()
jest.unstable_mockModule('../../lib/command/smartthings-command.js', () => ({
smartThingsCommand: smartThingsCommandMock,
smartThingsCommandBuilder: smartThingsCommandBuilderMock,
}))


const { default: cmd } = await import('../../commands/config.js')


jest.mock('../../lib/command/basic-io.js')
jest.mock('../../lib/command/command-util.js')
jest.mock('../../lib/command/output')
jest.mock('../../lib/command/output-builder.js')
jest.mock('../../lib/command/smartthings-command')
test('builder', () => {
const smartThingsCommandArgvMock = jest.fn() as jest.Mock<Argv<object & SmartThingsCommandFlags>> & Argv<object & SmartThingsCommandFlags>
smartThingsCommandBuilderMock.mockReturnValue(smartThingsCommandArgvMock)

const outputItemOrListArgvMock = jest.fn() as jest.Mock<Argv<object & SmartThingsCommandFlags>> & Argv<object & SmartThingsCommandFlags>

const optionMock = jest.fn() as jest.Mock<Argv<object & SmartThingsCommandFlags>['option']> & Argv<object & SmartThingsCommandFlags>['option']
optionMock.mockReturnValue(outputItemOrListArgvMock)
outputItemOrListArgvMock.option = optionMock

const positionalMock = jest.fn() as jest.Mock<Argv<object & SmartThingsCommandFlags>['positional']> & Argv<object & SmartThingsCommandFlags>['positional']
positionalMock.mockReturnValue(outputItemOrListArgvMock)
outputItemOrListArgvMock.positional = positionalMock

const exampleMock = jest.fn() as jest.Mock<Argv<object & SmartThingsCommandFlags>['example']> & Argv<object & SmartThingsCommandFlags>['example']
exampleMock.mockReturnValue(outputItemOrListArgvMock)
outputItemOrListArgvMock.example = exampleMock

outputItemOrListBuilderMock.mockReturnValueOnce(outputItemOrListArgvMock)

type CommandFlags = SmartThingsCommandFlags & { testOption?: string }
const yargsMock = jest.fn() as jest.Mock<Argv<CommandFlags>> & Argv<CommandFlags>

const builder = cmd.builder as (yargs: Argv<object>) => Argv<CommandArgs>
expect(builder(yargsMock)).toBe(outputItemOrListArgvMock)

expect(positionalMock).toHaveBeenCalledTimes(1)
expect(optionMock).toHaveBeenCalledTimes(1)
expect(exampleMock).toHaveBeenCalledTimes(1)
})

describe('handler', () => {
const profile1: Profile = {
Expand All @@ -28,21 +108,20 @@ describe('handler', () => {
const profile2WithName = { name: 'profile2', profile: profile2 }
const mergedProfiles: ProfilesByName = { profile1, profile2 }

const outputItemMock = jest.mocked(outputItem<ProfileWithName>).mockResolvedValue(profile1WithName)
const outputListMock = jest.mocked(outputList<ProfileWithName>).mockResolvedValue([profile1WithName, profile2WithName])
const stringTranslateToIdMock = jest.mocked(stringTranslateToId).mockResolvedValue('translated-id')
const calculateOutputFormatMock = jest.mocked(calculateOutputFormat).mockReturnValue('common')
const writeOutputMock = jest.mocked(writeOutput)
const outputFormatterMock = jest.fn().mockReturnValue('formatted output')
const buildOutputFormatterMock = jest.mocked(buildOutputFormatter).mockReturnValue(outputFormatterMock)
const smartThingsCommandMock = jest.mocked(smartThingsCommand)
outputItemMock.mockResolvedValue(profile1WithName)
outputListMock.mockResolvedValue([profile1WithName, profile2WithName])
stringTranslateToIdMock.mockResolvedValue('translated-id')
calculateOutputFormatMock.mockReturnValue('common')
const outputFormatterMock: jest.Mock<OutputFormatter<object>> = jest.fn()
outputFormatterMock.mockReturnValue('formatted output')
buildOutputFormatterMock.mockReturnValue(outputFormatterMock)

it('lists configs without args', async () => {
const inputArgv = { profile: 'profile1', verbose: false } as ArgumentsCamelCase<CommandArgs>
const command = { cliConfig: {
mergedProfiles,
} } as SmartThingsCommand<SmartThingsCommandFlags>
smartThingsCommandMock.mockResolvedValue(command)
const command = {
cliConfig: { mergedProfiles },
} as SmartThingsCommand<SmartThingsCommandFlags>
smartThingsCommandMock.mockResolvedValueOnce(command)

await expect(cmd.handler(inputArgv)).resolves.not.toThrow()

Expand All @@ -55,59 +134,93 @@ describe('handler', () => {
expect.objectContaining({ primaryKeyName: 'name' }),
expect.any(Function), true)

const outputListConfig = outputListMock.mock.calls[0][1]
const outputListConfig = outputListMock.mock.calls[0][1] as TableCommonListOutputProducer<object>
expect(outputListConfig.listTableFieldDefinitions?.length).toBe(2)

expect(stringTranslateToIdMock).toHaveBeenCalledTimes(0)
expect(outputItemMock).toHaveBeenCalledTimes(0)
expect(buildOutputFormatter).toHaveBeenCalledTimes(0)
expect(writeOutput).toHaveBeenCalledTimes(0)
expect(buildOutputFormatterMock).toHaveBeenCalledTimes(0)
expect(writeOutputMock).toHaveBeenCalledTimes(0)

const listFunction = outputListMock.mock.calls[0][2]
expect(await listFunction()).toStrictEqual([profile1WithName, profile2WithName])
})

it('marks running config in list', async () => {
const inputArgv = { profile: 'profile1', verbose: false } as ArgumentsCamelCase<CommandArgs>
const command = {
cliConfig: { mergedProfiles },
profileName: 'profile1',
} as SmartThingsCommand<SmartThingsCommandFlags>
smartThingsCommandMock.mockResolvedValueOnce(command)

await expect(cmd.handler(inputArgv)).resolves.not.toThrow()

expect(outputListMock).toHaveBeenCalledTimes(1)
const outputListConfig = outputListMock.mock.calls[0][1] as TableCommonListOutputProducer<Profile>
const fieldDefinition = outputListConfig.listTableFieldDefinitions[1] as ValueTableFieldDefinition<Profile>
const valueFunction = fieldDefinition.value

expect(fieldDefinition.label).toBe('Active')
expect(valueFunction(profile1WithName)).toBe('true')
expect(valueFunction(profile2WithName)).toBe('')
})

it('lists configs with extra fields when verbose requested', async () => {
const inputArgv = { profile: 'profile1', verbose: true } as ArgumentsCamelCase<CommandArgs>
const command = { cliConfig: {
mergedProfiles,
} } as SmartThingsCommand<SmartThingsCommandFlags>
smartThingsCommandMock.mockResolvedValue(command)
const command = {
cliConfig: { mergedProfiles },
} as SmartThingsCommand<SmartThingsCommandFlags>
smartThingsCommandMock.mockResolvedValueOnce(command)

await expect(cmd.handler(inputArgv)).resolves.not.toThrow()

expect(smartThingsCommandMock).toHaveBeenCalledTimes(1)
expect(smartThingsCommandMock).toHaveBeenCalledWith(inputArgv)
expect(calculateOutputFormatMock).toHaveBeenCalledTimes(1)
expect(calculateOutputFormatMock).toHaveBeenCalledWith(inputArgv)
expect(outputListMock).toHaveBeenCalledTimes(1)
expect(outputListMock).toHaveBeenCalledWith(command,
expect.objectContaining({ primaryKeyName: 'name' }),
expect.any(Function), true)

const outputListConfig = outputListMock.mock.calls[0][1]
const outputListConfig = outputListMock.mock.calls[0][1] as TableCommonListOutputProducer<object>
expect(outputListConfig.listTableFieldDefinitions?.length).toBe(3)
expect(outputListConfig.listTableFieldDefinitions?.[2]).toStrictEqual({ path: 'profile.token' })

expect(stringTranslateToIdMock).toHaveBeenCalledTimes(0)
expect(outputItemMock).toHaveBeenCalledTimes(0)
expect(buildOutputFormatter).toHaveBeenCalledTimes(0)
expect(writeOutput).toHaveBeenCalledTimes(0)

const listFunction = outputListMock.mock.calls[0][2]
expect(await listFunction()).toStrictEqual([profile1WithName, profile2WithName])
})

it('displays baseURL in list if there is one in any profile', async () => {
const inputArgv = { profile: 'profile1', verbose: true } as ArgumentsCamelCase<CommandArgs>
const profile3: Profile = {
key1: 'value of key 1',
clientIdProvider: { baseURL: 'https://test-url' },
}
const profile3WithName = { name: 'profile3', profile: profile3 }
const profiles: ProfilesByName = { ...mergedProfiles, profile3 }
const command = {
cliConfig: { mergedProfiles: profiles },
} as SmartThingsCommand<SmartThingsCommandFlags>
smartThingsCommandMock.mockResolvedValueOnce(command)

await expect(cmd.handler(inputArgv)).resolves.not.toThrow()

expect(outputListMock).toHaveBeenCalledTimes(1)

const outputListConfig = outputListMock.mock.calls[0][1] as TableCommonListOutputProducer<object>

const listFunction = outputListMock.mock.calls[0][2]
expect(await listFunction()).toStrictEqual([profile1WithName, profile2WithName, profile3WithName])
expect(outputListConfig.listTableFieldDefinitions.length).toBe(4)
expect(outputListConfig.listTableFieldDefinitions[3])
.toStrictEqual({ label: 'API URL', value: expect.any(Function) })
})

it('supports JSON or YAML output', async () => {
const inputArgv = { profile: 'profile1', verbose: false, output: 'output-file.json' } as ArgumentsCamelCase<CommandArgs>
const flags: SmartThingsCommandFlags = { profile: 'default1' }
const command = {
cliConfig: { mergedProfiles },
flags,
} as SmartThingsCommand<SmartThingsCommandFlags>
smartThingsCommandMock.mockResolvedValue(command)
smartThingsCommandMock.mockResolvedValueOnce(command)

calculateOutputFormatMock.mockReturnValue('json')
calculateOutputFormatMock.mockReturnValueOnce('json')

await expect(cmd.handler(inputArgv)).resolves.not.toThrow()

Expand All @@ -131,13 +244,13 @@ describe('handler', () => {
cliConfig: { mergedProfiles },
flags,
} as SmartThingsCommand<SmartThingsCommandFlags>
smartThingsCommandMock.mockResolvedValue(command)
smartThingsCommandMock.mockResolvedValueOnce(command)

calculateOutputFormatMock.mockReturnValue('json')
stringTranslateToIdMock.mockResolvedValue('profile2')
stringTranslateToIdMock.mockResolvedValueOnce('profile2')

await expect(cmd.handler(inputArgv)).resolves.not.toThrow()

expect(smartThingsCommandMock).toHaveBeenCalledTimes(1)
expect(stringTranslateToIdMock).toHaveBeenCalledTimes(1)
expect(stringTranslateToIdMock).toHaveBeenCalledWith(
expect.objectContaining({}),
Expand All @@ -156,4 +269,26 @@ describe('handler', () => {
const getFunction = outputItemMock.mock.calls[0][2]
expect(await getFunction()).toStrictEqual(profile2WithName)
})

it('displays full profile in detail view', async () => {
const inputArgv = { profile: 'profile1', verbose: false, name: 'profile2' } as ArgumentsCamelCase<CommandArgs>
const flags: SmartThingsCommandFlags = { profile: 'default1' }
const command = {
cliConfig: { mergedProfiles },
flags,
} as SmartThingsCommand<SmartThingsCommandFlags>
smartThingsCommandMock.mockResolvedValueOnce(command)

stringTranslateToIdMock.mockResolvedValueOnce('profile2')

await expect(cmd.handler(inputArgv)).resolves.not.toThrow()

expect(outputItemMock).toHaveBeenCalledTimes(1)
const outputItemConfig = outputItemMock.mock.calls[0][1] as TableCommonOutputProducer<Profile>
const fieldDefinition = outputItemConfig.tableFieldDefinitions[2] as ValueTableFieldDefinition<Profile>
const valueFunction = fieldDefinition.value

expect(fieldDefinition.label).toBe('Definition')
expect(valueFunction(profile1WithName)).toBe('key1: value1\nkey2: false\n')
})
})

0 comments on commit b0d070c

Please sign in to comment.