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",