diff --git a/src/lib/utils.js b/src/lib/utils.js index e87dba0..9591623 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -1,7 +1,6 @@ import terminalImage from 'terminal-image'; import Table from 'cli-table'; import { got } from 'got'; -import { select } from '@inquirer/prompts'; import { eightiesTrackIds, jazzTrackIds @@ -24,6 +23,7 @@ export function buildPlayQueue ({ tracks, currentTrack }) { * @description Prompt the user to choose which device to setup the playback */ export async function chooseSystemNode (system) { + const { select } = await import('@inquirer/prompts'); const discoveredNodes = await system.discoverNodes(); const choices = discoveredNodes .map((node) => ({ @@ -49,6 +49,7 @@ export async function chooseSystemNode (system) { * 4. Choose 'Share > Copy Song Link' and the track ID will be embedded in the link. Be sure to format it correctly when you paste it in the config */ export async function chooseVibe () { + const { select } = await import('@inquirer/prompts'); const choice = await select({ message: 'Aw yea! What type of mood would you like to set?', choices: [ @@ -80,10 +81,8 @@ export function randomTrack (items) { } export function camelize (str) { - return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => { - return index === 0 ? word.toLowerCase() : word.toUpperCase(); - }) - .replace(/\s+/g, '') - .replace(/'/g, '') - .replace(/'/g, ''); + return str + .replace(/[-\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '') // capitalize after dash/space + .replace(/'/g, '') // remove apostrophes + .replace(/^./, c => c.toLowerCase()); // lowercase first letter } diff --git a/test/lib/sonos.test.js b/test/lib/sonos.test.js index 055d133..9f89926 100644 --- a/test/lib/sonos.test.js +++ b/test/lib/sonos.test.js @@ -96,15 +96,9 @@ describe('sonos.js', () => { describe('getDevice', () => { beforeEach(async () => { await sonosInstance.discoverNodes(); - // Debug: print known nodes after discovery - // eslint-disable-next-line no-console - console.log('Known nodes after discovery:', sonosInstance.getKnownNodes()); }); it('should return device for valid key', async () => { - // Debug: print keys in _nodes - // eslint-disable-next-line no-console - console.log('Device for key livingRoom:', sonosInstance.getDevice({ key: 'livingRoom' })); const device = sonosInstance.getDevice({ key: 'livingRoom' }); expect(device).toBeDefined(); expect(device.name).toBe('Living Room'); diff --git a/test/lib/utils.test.js b/test/lib/utils.test.js new file mode 100644 index 0000000..0ac63e7 --- /dev/null +++ b/test/lib/utils.test.js @@ -0,0 +1,124 @@ +import { jest } from '@jest/globals'; +import { + buildPlayQueue, + printNowPlaying, + randomTrack, + camelize +} from '../../src/lib/utils.js'; + +// Mock external dependencies +jest.mock('terminal-image'); +jest.mock('cli-table'); +jest.mock('got'); + +describe('Utility Functions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('buildPlayQueue', () => { + it('should filter out the current track from the queue', () => { + const tracks = [ + { uri: 'spotify:track:1', title: 'Track 1' }, + { uri: 'spotify:track:2', title: 'Track 2' }, + { uri: 'spotify:track:3', title: 'Track 3' } + ]; + const currentTrack = { uri: 'spotify:track:2', title: 'Track 2' }; + + const result = buildPlayQueue({ tracks, currentTrack }); + + expect(result).toHaveLength(2); + expect(result).not.toContainEqual(currentTrack); + }); + + it('should return all tracks if current track is not in the list', () => { + const tracks = [ + { uri: 'spotify:track:1', title: 'Track 1' }, + { uri: 'spotify:track:2', title: 'Track 2' } + ]; + const currentTrack = { uri: 'spotify:track:3', title: 'Track 3' }; + + const result = buildPlayQueue({ tracks, currentTrack }); + + expect(result).toEqual(tracks); + }); + + it('should return all tracks if remaining tracks is empty', () => { + const tracks = [{ uri: 'spotify:track:1', title: 'Track 1' }]; + const currentTrack = { uri: 'spotify:track:1', title: 'Track 1' }; + + const result = buildPlayQueue({ tracks, currentTrack }); + + expect(result).toEqual(tracks); + }); + }); + + describe('chooseSystemNode', () => { + it('should return the selected node', async () => { + const mockNode = { name: 'Test Node' }; + const mockSystem = { + discoverNodes: jest.fn().mockResolvedValue([mockNode]) + }; + + await jest.resetModules(); + // ESM-compliant mocking + await jest.unstable_mockModule('@inquirer/prompts', () => ({ + select: jest.fn().mockResolvedValue(mockNode) + })); + + // Dynamically import after the mock is set up + const { chooseSystemNode } = await import('../../src/lib/utils.js'); + const result = await chooseSystemNode(mockSystem); + + expect(result).toEqual(mockNode); + }); + }); + + describe('chooseVibe', () => { + it('should return the selected vibe', async () => { + const mockVibe = ['track1', 'track2']; + await jest.resetModules(); + // ESM-compliant mocking + await jest.unstable_mockModule('@inquirer/prompts', () => ({ + select: jest.fn().mockResolvedValue(mockVibe) + })); + + // Dynamically import after the mock is set up + const { chooseVibe } = await import('../../src/lib/utils.js'); + const result = await chooseVibe(); + + expect(result).toBe(mockVibe); + // Optionally, check the prompt message and choices if needed + }); + }); + + describe('randomTrack', () => { + it('should return a random track from the array', () => { + const items = ['track1', 'track2', 'track3']; + const result = randomTrack(items); + expect(items).toContain(result); + }); + + it('should return undefined for empty array', () => { + const result = randomTrack([]); + expect(result).toBeUndefined(); + }); + }); + + describe('camelize', () => { + it('should convert string to camelCase', () => { + expect(camelize('hello world')).toBe('helloWorld'); + expect(camelize('Hello World')).toBe('helloWorld'); + expect(camelize('hello-world')).toBe('helloWorld'); + }); + + it('should handle special characters', () => { + expect(camelize("don't stop")).toBe('dontStop'); + expect(camelize("it's working")).toBe('itsWorking'); + }); + + it('should handle already camelCase strings', () => { + expect(camelize('helloWorld')).toBe('helloWorld'); + }); + }); +}); \ No newline at end of file