From b79085927265a4e7ab2ea3b59dbd59fbfc50ada7 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Sun, 28 Jun 2026 12:04:21 -0400 Subject: [PATCH] [tests] Add test coverage --- __tests__/components/PlaybackControls.test.js | 131 ++++++++ .../PlaybackControls.test.js.snap | 284 ++++++++++++++++++ package-lock.json | 4 +- 3 files changed, 417 insertions(+), 2 deletions(-) create mode 100644 __tests__/components/PlaybackControls.test.js create mode 100644 __tests__/components/__snapshots__/PlaybackControls.test.js.snap diff --git a/__tests__/components/PlaybackControls.test.js b/__tests__/components/PlaybackControls.test.js new file mode 100644 index 00000000..47f6bce6 --- /dev/null +++ b/__tests__/components/PlaybackControls.test.js @@ -0,0 +1,131 @@ +import React from 'react'; +import { act } from 'react-test-renderer'; +import { render } from '../test-wrapper'; +import PlaybackControls from '../../src/js/components/PlaybackControls'; +import state from '../state'; + +const mockState = { + ...state, + mopidy: { + ...state.mopidy, + time_position: 0, + volume: 50, + mute: false, + play_state: 'stopped', + consume: false, + random: false, + repeat: false, + host: 'localhost', + port: '6680', + ssl: false, + }, + ui: { + ...state.ui, + touch_enabled: false, + sidebar_open: false, + slim_mode: false, + }, + core: { + ...state.core, + streamTitle: null, + current_track: null, + next_track_uri: null, + }, +}; + +jest.mock('react-redux', () => ({ + useSelector: jest.fn().mockImplementation((func) => func(mockState)), + useDispatch: () => jest.fn(), +})); + +jest.mock('../../src/js/components/Icon', () => 'Icon'); +jest.mock('../../src/js/components/Link', () => 'Link'); +jest.mock('../../src/js/components/Thumbnail', () => 'Thumbnail'); +jest.mock('../../src/js/components/LinksSentence', () => 'LinksSentence'); +jest.mock('../../src/js/components/Fields/ProgressSlider', () => 'ProgressSlider'); +jest.mock('../../src/js/components/Fields/VolumeControl', () => 'VolumeControl'); +jest.mock('../../src/js/components/Fields/MuteControl', () => 'MuteControl'); +jest.mock('../../src/js/components/Fields/OutputControl', () => 'OutputControl'); + +function findSmartQueueButton(renderer) { + const buttons = renderer.root.findAllByType('button'); + return buttons.find((btn) => { + if (!btn.props.children) return false; + const children = Array.isArray(btn.props.children) + ? btn.props.children + : [btn.props.children]; + return children.some( + (child) => child.props && child.props.name === 'auto_awesome', + ); + }); +} + +describe('', () => { + beforeEach(() => { + localStorage.clear(); + jest.clearAllMocks(); + global.fetch = jest.fn(() => Promise.resolve({ ok: true })); + }); + + it('should render correctly', () => { + const result = render().toJSON(); + expect(result).toMatchSnapshot(); + }); + + it('should render smart queue button in settings section', () => { + const renderer = render(); + expect(findSmartQueueButton(renderer)).toBeTruthy(); + }); + + it('should have inactive smart queue button by default', () => { + const renderer = render(); + const btn = findSmartQueueButton(renderer); + expect(btn.props.className).not.toContain('control--active'); + }); + + it('should toggle active class on click and call fetch', () => { + const renderer = render(); + const btn = findSmartQueueButton(renderer); + act(() => { btn.props.onClick(); }); + + const newBtn = findSmartQueueButton(renderer); + expect(newBtn.props.className).toContain('control--active'); + expect(global.fetch).toHaveBeenCalledTimes(1); + expect(global.fetch).toHaveBeenCalledWith( + 'http://localhost:6680/smartplaylists/smart-queue', + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ enabled: true }), + }, + ); + }); + + it('should send enabled=false on second click', () => { + const renderer = render(); + let btn = findSmartQueueButton(renderer); + act(() => { btn.props.onClick(); }); + + btn = findSmartQueueButton(renderer); + act(() => { btn.props.onClick(); }); + + expect(global.fetch).toHaveBeenCalledTimes(2); + expect(global.fetch).toHaveBeenLastCalledWith( + 'http://localhost:6680/smartplaylists/smart-queue', + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ enabled: false }), + }, + ); + }); + + it('should restore smart queue state from localStorage', () => { + localStorage.setItem('smartQueue', 'true'); + const renderer = render(); + const btn = findSmartQueueButton(renderer); + expect(btn.props.className).toContain('control--active'); + }); + + +}); diff --git a/__tests__/components/__snapshots__/PlaybackControls.test.js.snap b/__tests__/components/__snapshots__/PlaybackControls.test.js.snap new file mode 100644 index 00000000..34289ad2 --- /dev/null +++ b/__tests__/components/__snapshots__/PlaybackControls.test.js.snap @@ -0,0 +1,284 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render correctly 1`] = ` +
+
+
+
+ + + +
+
+   +
+
+   +
+
+
+
+
+ + + +
+
+
+ - +
+ +
+ - +
+
+
+ + + + + +
+
+ + +
+
+ + +
+
+`; diff --git a/package-lock.json b/package-lock.json index 0bd34505..d2deeab6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mopidy-iris", - "version": "3.69.3", + "version": "3.70.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mopidy-iris", - "version": "3.69.3", + "version": "3.70.0", "license": "ISC", "dependencies": { "@sentry/browser": "7.5.0",