[tests] Add test coverage
All checks were successful
Deploy to mopidy1 / deploy (push) Successful in 34s

This commit is contained in:
2026-06-28 12:04:21 -04:00
parent 83ade4d4db
commit b790859272
3 changed files with 417 additions and 2 deletions

View File

@ -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('<PlaybackControls />', () => {
beforeEach(() => {
localStorage.clear();
jest.clearAllMocks();
global.fetch = jest.fn(() => Promise.resolve({ ok: true }));
});
it('should render correctly', () => {
const result = render(<PlaybackControls />).toJSON();
expect(result).toMatchSnapshot();
});
it('should render smart queue button in settings section', () => {
const renderer = render(<PlaybackControls />);
expect(findSmartQueueButton(renderer)).toBeTruthy();
});
it('should have inactive smart queue button by default', () => {
const renderer = render(<PlaybackControls />);
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(<PlaybackControls />);
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(<PlaybackControls />);
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(<PlaybackControls />);
const btn = findSmartQueueButton(renderer);
expect(btn.props.className).toContain('control--active');
});
});

View File

@ -0,0 +1,284 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<PlaybackControls /> should render correctly 1`] = `
<div
className="playback-controls"
data-qa-file="PlaybackControls"
data-qa-node="div"
>
<div
className="playback-controls__background"
data-qa-file="PlaybackControls"
data-qa-node="div"
/>
<div
className="current-track__wrapper"
data-qa-file="PlaybackControls"
data-qa-node="div"
>
<div
className="current-track"
data-qa-file="PlaybackControls"
data-qa-node="div"
tabIndex="-1"
>
<Link
className="thumbnail-wrapper"
data-qa-file="PlaybackControls"
data-qa-node="Link"
tabIndex="-1"
to="/modal/kiosk-mode"
>
<Thumbnail
data-qa-file="PlaybackControls"
data-qa-node="Thumbnail"
size="small"
type="track"
/>
</Link>
<div
className="text"
data-qa-file="PlaybackControls"
data-qa-node="div"
>
<div
className="title"
data-qa-file="PlaybackControls"
data-qa-node="div"
>
 
</div>
<div
className="artist"
data-qa-file="PlaybackControls"
data-qa-node="div"
>
 
</div>
</div>
</div>
</div>
<section
className="playback"
data-qa-file="PlaybackControls"
data-qa-node="section"
>
<button
className="control previous"
data-qa-file="PlaybackControls"
data-qa-node="button"
onClick={[Function]}
type="button"
>
<Icon
data-qa-file="PlaybackControls"
data-qa-node="Icon"
name="navigate_before"
type="material"
/>
</button>
<button
className="control play"
data-qa-file="PlaybackControls"
data-qa-node="button"
onClick={[Function]}
type="button"
>
<Icon
data-qa-file="PlaybackControls"
data-qa-node="Icon"
name="play_circle_filled"
type="material"
/>
</button>
<button
className="control next"
data-qa-file="PlaybackControls"
data-qa-node="button"
onClick={[Function]}
type="button"
>
<Icon
data-qa-file="PlaybackControls"
data-qa-node="Icon"
name="navigate_next"
type="material"
/>
</button>
</section>
<section
className="progress"
data-qa-file="PlaybackControls"
data-qa-node="section"
>
<div
className="time time--current"
data-qa-file="PlaybackControls"
data-qa-node="div"
>
-
</div>
<ProgressSlider
data-qa-file="PlaybackControls"
data-qa-node="ProgressSlider"
playbackPosition={0}
/>
<div
className="time time--total"
data-qa-file="PlaybackControls"
data-qa-node="div"
>
-
</div>
</section>
<section
className="settings"
data-qa-file="PlaybackControls"
data-qa-node="section"
>
<button
className="control tooltip"
data-qa-file="PlaybackControls"
data-qa-node="button"
onClick={[Function]}
type="button"
>
<Icon
data-qa-file="PlaybackControls"
data-qa-node="Icon"
name="restaurant"
type="material"
/>
<span
className="tooltip__content"
data-qa-file="PlaybackControls"
data-qa-node="span"
>
Consume
</span>
</button>
<button
className="control tooltip"
data-qa-file="PlaybackControls"
data-qa-node="button"
onClick={[Function]}
type="button"
>
<Icon
data-qa-file="PlaybackControls"
data-qa-node="Icon"
name="auto_awesome"
type="material"
/>
<span
className="tooltip__content"
data-qa-file="PlaybackControls"
data-qa-node="span"
>
Smart Queue
</span>
</button>
<button
className="control tooltip"
data-qa-file="PlaybackControls"
data-qa-node="button"
onClick={[Function]}
type="button"
>
<Icon
data-qa-file="PlaybackControls"
data-qa-node="Icon"
name="shuffle"
type="material"
/>
<span
className="tooltip__content"
data-qa-file="PlaybackControls"
data-qa-node="span"
>
Shuffle
</span>
</button>
<button
className="control tooltip"
data-qa-file="PlaybackControls"
data-qa-node="button"
onClick={[Function]}
type="button"
>
<Icon
data-qa-file="PlaybackControls"
data-qa-node="Icon"
name="repeat"
type="material"
/>
<span
className="tooltip__content"
data-qa-file="PlaybackControls"
data-qa-node="span"
>
Repeat
</span>
</button>
<OutputControl
data-qa-file="PlaybackControls"
data-qa-node="OutputControl"
/>
</section>
<section
className="volume"
data-qa-file="PlaybackControls"
data-qa-node="section"
>
<MuteControl
data-qa-file="PlaybackControls"
data-qa-node="MuteControl"
mute={false}
onMuteChange={[Function]}
/>
<VolumeControl
data-qa-file="PlaybackControls"
data-qa-node="VolumeControl"
mute={false}
onVolumeChange={[Function]}
scrollWheel={true}
volume={50}
/>
</section>
<section
className="triggers"
data-qa-file="PlaybackControls"
data-qa-node="section"
>
<button
className="control expanded-controls"
data-qa-file="PlaybackControls"
data-qa-node="button"
onClick={[Function]}
type="button"
>
<Icon
data-qa-file="PlaybackControls"
data-qa-node="Icon"
name="expand_less"
type="material"
/>
</button>
<button
className="control sidebar-toggle"
data-qa-file="PlaybackControls"
data-qa-node="button"
onClick={[Function]}
type="button"
>
<Icon
className="open"
data-qa-file="PlaybackControls"
data-qa-node="Icon"
name="menu"
type="material"
/>
</button>
</section>
</div>
`;