Skip to content

Commit

Permalink
refactor: move devicepreferences:translations:create command to yargs
Browse files Browse the repository at this point in the history
  • Loading branch information
rossiam committed Oct 14, 2024
1 parent 2337cf3 commit e531981
Show file tree
Hide file tree
Showing 10 changed files with 295 additions and 92 deletions.
33 changes: 0 additions & 33 deletions packages/cli/src/commands/devicepreferences/translations/create.ts

This file was deleted.

This file was deleted.

118 changes: 118 additions & 0 deletions src/__tests__/commands/devicepreferences/translations/create.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { jest } from '@jest/globals'

import type { ArgumentsCamelCase, Argv } from 'yargs'

import type {
DevicePreferencesEndpoint,
PreferenceLocalization,
SmartThingsClient,
} from '@smartthings/core-sdk'

import type { CommandArgs } from '../../../../commands/devicepreferences/translations/create.js'
import type {
APIOrganizationCommand,
APIOrganizationCommandFlags,
apiOrganizationCommand,
apiOrganizationCommandBuilder,
} from '../../../../lib/command/api-organization-command.js'
import type {
inputAndOutputItem,
inputAndOutputItemBuilder,
} from '../../../../lib/command/basic-io.js'
import type { chooseDevicePreference } from '../../../../lib/command/util/devicepreferences-util.js'
import {
tableFieldDefinitions,
} from '../../../../lib/command/util/devicepreferences/translations-util.js'
import { buildArgvMock, buildArgvMockStub } from '../../../test-lib/builder-mock.js'


const apiOrganizationCommandMock = jest.fn<typeof apiOrganizationCommand>()
const apiOrganizationCommandBuilderMock = jest.fn<typeof apiOrganizationCommandBuilder>()
jest.unstable_mockModule('../../../../lib/command/api-organization-command.js', () => ({
apiOrganizationCommand: apiOrganizationCommandMock,
apiOrganizationCommandBuilder: apiOrganizationCommandBuilderMock,
}))

const inputAndOutputItemMock = jest.fn<typeof inputAndOutputItem>()
const inputAndOutputItemBuilderMock = jest.fn<typeof inputAndOutputItemBuilder>()
jest.unstable_mockModule('../../../../lib/command/basic-io.js', () => ({
inputAndOutputItem: inputAndOutputItemMock,
inputAndOutputItemBuilder: inputAndOutputItemBuilderMock,
}))

const chooseDevicePreferenceMock = jest.fn<typeof chooseDevicePreference>()
jest.unstable_mockModule('../../../../lib/command/util/devicepreferences-util.js', () => ({
chooseDevicePreference: chooseDevicePreferenceMock,
}))


const {
default: cmd,
} = await import('../../../../commands/devicepreferences/translations/create.js')


test('builder', () => {
const yargsMock = buildArgvMockStub<object>()
const {
yargsMock: apiOrganizationCommandBuilderArgvMock,
positionalMock,
exampleMock,
argvMock,
} = buildArgvMock<APIOrganizationCommandFlags, CommandArgs>()

apiOrganizationCommandBuilderMock.mockReturnValueOnce(apiOrganizationCommandBuilderArgvMock)
inputAndOutputItemBuilderMock.mockReturnValueOnce(argvMock)

const builder = cmd.builder as (yargs: Argv<object>) => Argv<CommandArgs>

expect(builder(yargsMock)).toBe(argvMock)

expect(apiOrganizationCommandBuilderMock).toHaveBeenCalledExactlyOnceWith(yargsMock)
expect(inputAndOutputItemBuilderMock)
.toHaveBeenCalledExactlyOnceWith(apiOrganizationCommandBuilderArgvMock)

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

test('handler', async () => {
const apiDevicePreferencesCreateTranslationsMock =
jest.fn<typeof DevicePreferencesEndpoint.prototype['createTranslations']>()

const clientMock = {
devicePreferences: {
createTranslations: apiDevicePreferencesCreateTranslationsMock,
},
} as unknown as SmartThingsClient
const command = {
client: clientMock,
} as APIOrganizationCommand<CommandArgs>
apiOrganizationCommandMock.mockResolvedValueOnce(command)
chooseDevicePreferenceMock.mockResolvedValueOnce('chosen-id')

const inputArgv = {
profile: 'default',
devicePreferenceId: 'argv-id',
} as unknown as ArgumentsCamelCase<CommandArgs>
await expect(cmd.handler(inputArgv)).resolves.not.toThrow()

expect(apiOrganizationCommandMock).toHaveBeenCalledExactlyOnceWith(inputArgv)
expect(chooseDevicePreferenceMock).toHaveBeenCalledExactlyOnceWith(command, 'argv-id')
expect(inputAndOutputItemMock).toHaveBeenCalledExactlyOnceWith(
command,
{ tableFieldDefinitions },
expect.any(Function),
)

const create = inputAndOutputItemMock.mock.calls[0][2]

const translationCreate = { description: 'Translation To Create' } as PreferenceLocalization
const createdTranslation = { description: 'Created Translation' } as PreferenceLocalization

apiDevicePreferencesCreateTranslationsMock.mockResolvedValueOnce(createdTranslation)

expect(await create(undefined, translationCreate)).toBe(createdTranslation)

expect(apiDevicePreferencesCreateTranslationsMock)
.toHaveBeenCalledExactlyOnceWith('chosen-id', translationCreate)
})
53 changes: 28 additions & 25 deletions src/__tests__/lib/command/util/devicepreferences-util.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { jest } from '@jest/globals'

import { DevicePreference, DevicePreferencesEndpoint, SmartThingsClient } from '@smartthings/core-sdk'
import type {
DevicePreference,
DevicePreferencesEndpoint,
SmartThingsClient,
} from '@smartthings/core-sdk'

import { APICommand } from '../../../../lib/command/api-command.js'
import { SelectFromListFlags, selectFromList } from '../../../../lib/command/select.js'
import { ValueTableFieldDefinition } from '../../../../lib/table-generator.js'
import type { ValueTableFieldDefinition } from '../../../../lib/table-generator.js'
import type { ChooseFunction, createChooseFn } from '../../../../lib/command/util/util-util.js'


const selectFromListMock = jest.fn<typeof selectFromList>()
jest.unstable_mockModule('../../../../lib/command/select.js', () => ({
selectFromList: selectFromListMock,
const createChooseFnMock = jest.fn<typeof createChooseFn<DevicePreference>>()
jest.unstable_mockModule('../../../../lib/command/util/util-util.js', () => ({
createChooseFn: createChooseFnMock,
}))


const {
chooseDevicePreference,
chooseDevicePreferenceFn,
tableFieldDefinitions,
} = await import('../../../../lib/command/util/devicepreferences-util.js')

Expand All @@ -39,29 +42,29 @@ describe('tableFieldDefinitions options definition', () => {
})

test('chooseDevicePreference', async () => {
selectFromListMock.mockResolvedValueOnce('chosen-id')
const listMock = jest.fn<typeof DevicePreferencesEndpoint.prototype.list>()
const client = { devicePreferences: { list: listMock } } as unknown as SmartThingsClient
const command = { client } as APICommand<SelectFromListFlags>
const chooseAppMock = jest.fn<ChooseFunction<DevicePreference>>()
createChooseFnMock.mockReturnValueOnce(chooseAppMock)

expect(await chooseDevicePreference(command, 'preselected-id')).toBe('chosen-id')
const chooseApp = chooseDevicePreferenceFn()

expect(selectFromListMock).toHaveBeenCalledTimes(1)
expect(selectFromListMock).toHaveBeenCalledWith(
command,
expect(chooseApp).toBe(chooseAppMock)

expect(createChooseFnMock).toHaveBeenCalledExactlyOnceWith(
expect.objectContaining({ itemName: 'device preference' }),
{
preselectedId: 'preselected-id',
listItems: expect.any(Function),
},
expect.any(Function),
)

const listItems = selectFromListMock.mock.calls[0][2].listItems
const devicePreferenceList = [{ preferenceId: 'device-preference-id' } as DevicePreference]
listMock.mockResolvedValueOnce(devicePreferenceList)
const apiDevicePreferencesListMock = jest.fn<typeof DevicePreferencesEndpoint.prototype.list>()
.mockResolvedValueOnce(devicePreferenceList)
const listItems = createChooseFnMock.mock.calls[0][1]
const client = {
devicePreferences: {
list: apiDevicePreferencesListMock,
},
} as unknown as SmartThingsClient

expect(await listItems()).toBe(devicePreferenceList)
expect(await listItems(client)).toBe(devicePreferenceList)

expect(listMock).toHaveBeenCalledTimes(1)
expect(listMock).toHaveBeenCalledWith()
expect(apiDevicePreferencesListMock).toHaveBeenCalledExactlyOnceWith()
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { PreferenceLocalization } from '@smartthings/core-sdk'

import { ValueTableFieldDefinition } from '../../../../../lib/table-generator.js'
import {
tableFieldDefinitions,
} from '../../../../../lib/command/util/devicepreferences/translations-util.js'


const unpopulatedOptions: (Record<string, { label: string }> | null | undefined)[] = [
null,
undefined,
{},
]

const populatedOptions: ({
options: Record<string, { label: string }>
expected: string
})[] = [
{ options: { key: { label: 'Translation' } }, expected: 'key: Translation' },
{
options: { key1: { label: 'Translation 1' }, key2: { label: 'Translation 2' } },
expected: 'key1: Translation 1\nkey2: Translation 2',
},
]

const definition = (tableFieldDefinitions[3] as ValueTableFieldDefinition<PreferenceLocalization>)

describe('options include function', () => {
const include = definition.include

it.each(unpopulatedOptions)('returns false for unpopulated options', (options) => {
expect(include?.({ options } as PreferenceLocalization)).toBeFalse()
})

it.each(populatedOptions)('returns true for populated options', ({ options }) => {
expect(include?.({ options } as PreferenceLocalization)).toBeTrue()
})
})

describe('options value function', () => {
const value = definition.value

it.each(unpopulatedOptions)('returns falsy for unpopulated options', (options) => {
expect(value?.({ options } as PreferenceLocalization)).toBeFalsy()
})

it.each(populatedOptions)(
'returns string representation for populated options',
({ options, expected }) => {
expect(value?.({ options } as PreferenceLocalization)).toBe(expected)
},
)
})
56 changes: 56 additions & 0 deletions src/commands/devicepreferences/translations/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ArgumentsCamelCase, Argv, CommandModule } from 'yargs'

import { PreferenceLocalization } from '@smartthings/core-sdk'

import {
apiOrganizationCommand,
apiOrganizationCommandBuilder,
type APIOrganizationCommandFlags,
} from '../../../lib/command/api-organization-command.js'
import {
inputAndOutputItem,
inputAndOutputItemBuilder,
InputAndOutputItemFlags,
} from '../../../lib/command/basic-io.js'
import { chooseDevicePreference } from '../../../lib/command/util/devicepreferences-util.js'
import {
tableFieldDefinitions,
} from '../../../lib/command/util/devicepreferences/translations-util.js'


export type CommandArgs =
& APIOrganizationCommandFlags
& InputAndOutputItemFlags
& {
devicePreferenceId?: string
}

const command = 'devicepreferences:translations:create [device-preference-id]'

const describe = 'create a translation for a device preference'

const builder = (yargs: Argv): Argv<CommandArgs> =>
inputAndOutputItemBuilder(apiOrganizationCommandBuilder(yargs))
.positional('device-preference-id', { describe: 'device preference id', type: 'string' })
.example([
[
'$0 devicepreferences:translations:create -i preferenceTranslation.json',
'create a translation as defined in preferenceTranslation.json',
],
])

const handler = async (argv: ArgumentsCamelCase<CommandArgs>): Promise<void> => {
const command = await apiOrganizationCommand(argv)

const preferenceId = await chooseDevicePreference(command, argv.devicePreferenceId)

await inputAndOutputItem<PreferenceLocalization, PreferenceLocalization>(
command,
{ tableFieldDefinitions },
(_, translation) =>
command.client.devicePreferences.createTranslations(preferenceId, translation),
)
}

const cmd: CommandModule<object, CommandArgs> = { command, describe, builder, handler }
export default cmd
2 changes: 2 additions & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CommandModule } from 'yargs'
import appsCommand from './apps.js'
import configCommand from './config.js'
import devicepreferencesCommand from './devicepreferences.js'
import devicepreferencesTranslationsCreateCommand from './devicepreferences/translations/create.js'
import deviceprofilesCommand from './deviceprofiles.js'
import devicesCapabilityStatusCommand from './devices/capability-status.js'
import devicesPreferencesCommand from './devices/preferences.js'
Expand All @@ -17,6 +18,7 @@ export const commands: CommandModule<object, any>[] = [
appsCommand,
configCommand,
devicepreferencesCommand,
devicepreferencesTranslationsCreateCommand,
deviceprofilesCommand,
devicesCapabilityStatusCommand,
devicesPreferencesCommand,
Expand Down
Loading

0 comments on commit e531981

Please sign in to comment.