Compare commits

...

5 Commits

Author SHA1 Message Date
Keith Edmunds
836d812ef3 Migrate to Alchemical and unittest+pytest framework 2024-04-05 17:47:26 +01:00
Keith Edmunds
6624ac8f31 Fix up db import 2024-04-05 17:29:06 +01:00
Keith Edmunds
c5595bb61b All tests working 2024-04-05 16:42:02 +01:00
Keith Edmunds
92d85304f2 Put commit()s where needed, move some info to debug logging 2024-04-05 14:42:04 +01:00
Keith Edmunds
e813a80a5b Debugging for #223 2024-04-05 11:23:00 +01:00
8 changed files with 404 additions and 394 deletions

View File

@ -78,7 +78,7 @@ class NoteColours(dbtables.NoteColoursTable):
self.order = order
session.add(self)
session.flush()
session.commit()
@classmethod
def get_all(cls, session: Session) -> Sequence["NoteColours"]:
@ -162,7 +162,7 @@ class Playlists(dbtables.PlaylistsTable):
def __init__(self, session: Session, name: str):
self.name = name
session.add(self)
session.flush()
session.commit()
@staticmethod
def clear_tabs(session: Session, playlist_ids: List[int]) -> None:
@ -201,7 +201,7 @@ class Playlists(dbtables.PlaylistsTable):
"""
self.deleted = True
session.flush()
session.commit()
@classmethod
def get_all(cls, session: Session) -> Sequence["Playlists"]:
@ -268,7 +268,7 @@ class Playlists(dbtables.PlaylistsTable):
"""
self.name = new_name
session.flush()
session.commit()
@staticmethod
def save_as_template(
@ -381,7 +381,7 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
PlaylistRows.plr_rownum > maxrow,
)
)
session.flush()
session.commit()
@staticmethod
def delete_row(session: Session, playlist_id: int, row_number: int) -> None:
@ -575,7 +575,7 @@ class Settings(dbtables.SettingsTable):
def __init__(self, session: Session, name: str):
self.name = name
session.add(self)
session.flush()
session.commit()
@classmethod
def all_as_dict(cls, session):
@ -605,7 +605,7 @@ class Settings(dbtables.SettingsTable):
for key, value in data.items():
assert hasattr(self, key)
setattr(self, key, value)
session.flush()
session.commit()
class Tracks(dbtables.TracksTable):

View File

@ -59,10 +59,9 @@ from classes import (
PlaylistTrack,
)
from config import Config
from dbtables import db
from dialogs import TrackSelectDialog
from log import log
from models import Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks
from models import db, Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks
from playlistmodel import PlaylistModel, PlaylistProxyModel
from playlists import PlaylistTab
from ui import icons_rc # noqa F401
@ -1118,34 +1117,15 @@ class Window(QMainWindow, Ui_MainWindow):
# Restore volume if -3dB active
if self.btnDrop3db.isChecked():
log.debug("Reset -3db button")
self.btnDrop3db.setChecked(False)
# Show closing volume graph
if track_sequence.now.fade_graph:
track_sequence.now.fade_graph.plot()
else:
log.error("No fade_graph")
# Play (new) current track
if not track_sequence.now.path:
log.error("No path for next track")
return
self.music.play(track_sequence.now.path, position)
# Note that track is playing
track_sequence.now.start()
self.playing = True
# Disable play next controls
self.catch_return_key = True
self.show_status_message("Play controls: Disabled", 0)
# Notify model
self.active_proxy_model().current_track_started()
# Update headers
self.update_headers()
# Ensure 100% volume
# For as-yet unknown reasons. sometimes the volume gets
# reset to zero within 200mS or so of starting play. This
@ -1160,6 +1140,36 @@ class Window(QMainWindow, Ui_MainWindow):
break
sleep(0.1)
# TODO: remove sleep() calls - used to try to isolate bug #223
# Show closing volume graph
sleep(1)
if track_sequence.now.fade_graph:
track_sequence.now.fade_graph.plot()
else:
log.error("No fade_graph")
# Note that track is playing
sleep(1)
log.error("set track_sequence")
track_sequence.now.start()
self.playing = True
# Disable play next controls
sleep(1)
log.error("catch return key")
self.catch_return_key = True
self.show_status_message("Play controls: Disabled", 0)
# Notify model
sleep(1)
log.error("active_proxy_model().current_track_started()")
self.active_proxy_model().current_track_started()
# Update headers
sleep(1)
log.error("update headers")
self.update_headers()
def preview(self) -> None:
"""
Preview selected or next track. We use a different mechanism to

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from enum import auto, Enum
from operator import attrgetter
from time import sleep
from random import shuffle
from typing import List, Optional
import datetime as dt
@ -123,7 +124,7 @@ class PlaylistModel(QAbstractTableModel):
*args,
**kwargs,
):
log.info(f"PlaylistModel.__init__({playlist_id=})")
log.debug(f"PlaylistModel.__init__({playlist_id=})")
self.playlist_id = playlist_id
super().__init__(*args, **kwargs)
@ -155,7 +156,7 @@ class PlaylistModel(QAbstractTableModel):
Add track to existing header row
"""
log.info(f"add_track_to_header({row_number=}, {track_id=}, {note=}")
log.debug(f"add_track_to_header({row_number=}, {track_id=}, {note=}")
# Get existing row
try:
@ -278,14 +279,21 @@ class PlaylistModel(QAbstractTableModel):
)
return
# TODO: remove sleep/log calls, used to debug #223
# Check for OBS scene change
sleep(1)
log.error("Call OBS scene change")
self.obs_scene_change(row_number)
with db.Session() as session:
# Update Playdates in database
sleep(1)
log.error("update playdates")
Playdates(session, track_sequence.now.track_id)
# Mark track as played in playlist
sleep(1)
log.error("Mark track as played")
plr = session.get(PlaylistRows, track_sequence.now.plr_id)
if plr:
plr.played = True
@ -294,6 +302,8 @@ class PlaylistModel(QAbstractTableModel):
log.error(f"Can't retrieve plr, {track_sequence.now.plr_id=}")
# Update track times
sleep(1)
log.error("Update track times")
if prd:
prd.start_time = track_sequence.now.start_time
prd.end_time = track_sequence.now.end_time
@ -310,6 +320,8 @@ class PlaylistModel(QAbstractTableModel):
# Find next track
# Get all unplayed track rows
sleep(1)
log.error("Find next track")
next_row = None
unplayed_rows = self.get_unplayed_rows()
if unplayed_rows:
@ -543,7 +555,7 @@ class PlaylistModel(QAbstractTableModel):
If not given, return row number to add to end of model.
"""
log.info(f"_get_new_row_number({proposed_row_number=})")
log.debug(f"_get_new_row_number({proposed_row_number=})")
if proposed_row_number is None or proposed_row_number > len(self.playlist_rows):
# We are adding to the end of the list
@ -840,7 +852,7 @@ class PlaylistModel(QAbstractTableModel):
Move the playlist rows given to to_row and below.
"""
log.info(f"move_rows({from_rows=}, {to_row_number=}")
log.debug(f"move_rows({from_rows=}, {to_row_number=}")
# Build a {current_row_number: new_row_number} dictionary
row_map: dict[int, int] = {}
@ -904,7 +916,7 @@ class PlaylistModel(QAbstractTableModel):
Move the playlist rows given to to_row and below of to_playlist.
"""
log.info(
log.debug(
f"move_rows_between_playlists({from_rows=}, {to_row_number=}, {to_playlist_id=}"
)
@ -1102,7 +1114,7 @@ class PlaylistModel(QAbstractTableModel):
Signal handler for when row ordering has changed
"""
log.info("reset_track_sequence_row_numbers()")
log.debug("reset_track_sequence_row_numbers()")
# Check the track_sequence next, now and previous plrs and
# update the row number
@ -1134,7 +1146,7 @@ class PlaylistModel(QAbstractTableModel):
return: [[20, 21], [17], [13], [9, 10], [7], [2, 3, 4, 5]]
"""
log.info(f"_reversed_contiguous_row_groups({row_numbers=} called")
log.debug(f"_reversed_contiguous_row_groups({row_numbers=} called")
result: List[List[int]] = []
temp: List[int] = []
@ -1150,7 +1162,7 @@ class PlaylistModel(QAbstractTableModel):
result.append(temp)
result.reverse()
log.info(f"_reversed_contiguous_row_groups() returned: {result=}")
log.debug(f"_reversed_contiguous_row_groups() returned: {result=}")
return result
def rowCount(self, index: QModelIndex = QModelIndex()) -> int:
@ -1163,7 +1175,7 @@ class PlaylistModel(QAbstractTableModel):
Signal handler for when row ordering has changed
"""
log.info(f"row_order_changed({playlist_id=}) {self.playlist_id=}")
log.debug(f"row_order_changed({playlist_id=}) {self.playlist_id=}")
# Only action if this is for us
if playlist_id != self.playlist_id:

View File

@ -36,7 +36,6 @@ from PyQt6.QtWidgets import (
# App imports
from classes import MusicMusterSignals, track_sequence
from config import Config
from dbtables import db
from dialogs import TrackSelectDialog
from helpers import (
ask_yes_no,
@ -45,7 +44,7 @@ from helpers import (
show_warning,
)
from log import log
from models import Settings
from models import db, Settings
from playlistmodel import PlaylistModel, PlaylistProxyModel
if TYPE_CHECKING:

View File

@ -1,5 +1,7 @@
# Standard library imports
import datetime as dt
import shutil
import tempfile
import unittest
# PyQt imports
@ -14,6 +16,7 @@ from helpers import (
get_relative_date,
leading_silence,
ms_to_mmss,
normalise_track,
)
@ -85,3 +88,14 @@ class TestMMHelpers(unittest.TestCase):
assert ms_to_mmss(None) == "-"
assert ms_to_mmss(59600) == "0:59"
assert ms_to_mmss((5 * 60 * 1000) + 23000) == "5:23"
def test_normalise(self):
"""Make copies to normalise to avoid corrupting source"""
_, mp3_temp_path = tempfile.mkstemp(suffix=".mp3")
shutil.copyfile("testdata/isa.mp3", mp3_temp_path)
normalise_track(mp3_temp_path)
_, flac_temp_path = tempfile.mkstemp(suffix=".flac")
shutil.copyfile("testdata/isa.flac", flac_temp_path)
normalise_track(flac_temp_path)

View File

@ -35,8 +35,6 @@ class TestMMModels(unittest.TestCase):
track1_path = "testdata/isa.mp3"
metadata1 = helpers.get_file_metadata(track1_path)
self.track1 = Tracks(session, **metadata1)
# Test repr
_ = str(self.track1)
track2_path = "testdata/mom.mp3"
metadata2 = helpers.get_file_metadata(track2_path)
@ -45,6 +43,11 @@ class TestMMModels(unittest.TestCase):
def tearDown(self):
db.drop_all()
def test_track_repr(self):
with db.Session() as session:
session.add(self.track1)
_ =str(self.track1)
def test_notecolours_get_colour(self):
"""Create a colour record and retrieve all colours"""

View File

@ -28,61 +28,114 @@ from app.models import ( # noqa: E402
)
# class TestMMMiscTracks(unittest.TestCase):
# def setUp(self):
# PLAYLIST_NAME = "tracks playlist"
# self.test_tracks = [
# "testdata/isa.mp3",
# "testdata/isa_with_gap.mp3",
# "testdata/loser.mp3",
# "testdata/lovecats-10seconds.mp3",
# "testdata/lovecats.mp3",
# "testdata/mom.mp3",
# "testdata/sitting.mp3",
# ]
# db.create_all()
# # Create a playlist and model
# with db.Session() as session:
# self.playlist = Playlists(session, PLAYLIST_NAME)
# self.model = playlistmodel.PlaylistModel(self.playlist.id)
# for row in range(len(self.test_tracks)):
# track_path = self.test_tracks[row % len(self.test_tracks)]
# metadata = get_file_metadata(track_path)
# track = Tracks(session, **metadata)
# self.model.insert_row(
# proposed_row_number=row, track_id=track.id, note=f"{row=}"
# )
# session.commit()
# def tearDown(self):
# db.drop_all()
# def test_7_row_playlist(self):
# # Test auto-created playlist
# assert self.model.rowCount() == 7
# assert max(self.model.playlist_rows.keys()) == 6
# for row in range(self.model.rowCount()):
# assert row in self.model.playlist_rows
# assert self.model.playlist_rows[row].plr_rownum == row
class TestMMMiscRowMove(unittest.TestCase):
class TestMMMiscTracks(unittest.TestCase):
def setUp(self):
PLAYLIST_NAME = "rowmove playlist"
ROWS_TO_CREATE = 11
PLAYLIST_NAME = "tracks playlist"
self.test_tracks = [
"testdata/isa.mp3",
"testdata/isa_with_gap.mp3",
"testdata/loser.mp3",
"testdata/lovecats-10seconds.mp3",
"testdata/lovecats.mp3",
"testdata/mom.mp3",
"testdata/sitting.mp3",
"testdata/wrb.flac",
]
db.create_all()
# Create a playlist and model
with db.Session() as session:
self.playlist = Playlists(session, PLAYLIST_NAME)
self.model = playlistmodel.PlaylistModel(self.playlist.id)
for row in range(ROWS_TO_CREATE):
print(f"{row=}")
for row in range(len(self.test_tracks)):
track_path = self.test_tracks[row % len(self.test_tracks)]
metadata = get_file_metadata(track_path)
track = Tracks(session, **metadata)
self.model.insert_row(
proposed_row_number=row, track_id=track.id, note=f"{row=}"
)
session.commit()
def tearDown(self):
db.drop_all()
def test_8_row_playlist(self):
# Test auto-created playlist
assert self.model.rowCount() == 8
assert max(self.model.playlist_rows.keys()) == 7
for row in range(self.model.rowCount()):
assert row in self.model.playlist_rows
assert self.model.playlist_rows[row].plr_rownum == row
def test_timing_one_track(self):
START_ROW = 0
END_ROW = 2
self.model.insert_row(proposed_row_number=START_ROW, note="start+")
self.model.insert_row(proposed_row_number=END_ROW, note="-")
prd = self.model.playlist_rows[START_ROW]
qv_value = self.model.display_role(START_ROW, playlistmodel.HEADER_NOTES_COLUMN, prd)
assert qv_value.value() == "start [1 tracks, 4:23 unplayed]"
class TestMMMiscNoPlaylist(unittest.TestCase):
PLAYLIST_NAME = "tracks playlist"
test_tracks = [
"testdata/isa.mp3",
"testdata/isa_with_gap.mp3",
"testdata/loser.mp3",
"testdata/lovecats-10seconds.mp3",
"testdata/lovecats.mp3",
"testdata/mom.mp3",
"testdata/sitting.mp3",
]
def setUp(self):
db.create_all()
def tearDown(self):
db.drop_all()
def test_insert_track_new_playlist(self):
# insert a track into a new playlist
with db.Session() as session:
playlist = Playlists(session, self.PLAYLIST_NAME)
# Create a model
model = playlistmodel.PlaylistModel(playlist.id)
# test repr
_ = str(model)
track_path = self.test_tracks[0]
metadata = get_file_metadata(track_path)
track = Tracks(session, **metadata)
model.insert_row(proposed_row_number=0, track_id=track.id)
prd = model.playlist_rows[model.rowCount() - 1]
# test repr
_ = str(prd)
assert (
model.edit_role(model.rowCount() - 1, playlistmodel.Col.TITLE.value, prd)
== metadata["title"]
)
class TestMMMiscRowMove(unittest.TestCase):
PLAYLIST_NAME = "rowmove playlist"
ROWS_TO_CREATE = 11
def setUp(self):
db.create_all()
with db.Session() as session:
self.playlist = Playlists(session, self.PLAYLIST_NAME)
self.model = playlistmodel.PlaylistModel(self.playlist.id)
for row in range(self.ROWS_TO_CREATE):
self.model.insert_row(proposed_row_number=row, note=str(row))
session.commit()
@ -106,311 +159,230 @@ class TestMMMiscRowMove(unittest.TestCase):
elif row == 5:
assert self.model.playlist_rows[row].note == str(3)
def test_move_rows_test3(self):
# move row 4 to row 3
self.model.move_rows([4], 3)
# Check we have all rows and plr_rownums are correct
for row in range(self.model.rowCount()):
assert row in self.model.playlist_rows
assert self.model.playlist_rows[row].plr_rownum == row
if row not in [3, 4]:
assert self.model.playlist_rows[row].note == str(row)
elif row == 3:
assert self.model.playlist_rows[row].note == str(4)
elif row == 4:
assert self.model.playlist_rows[row].note == str(3)
def test_move_rows_test4(self):
# move row 4 to row 2
self.model.move_rows([4], 2)
# Check we have all rows and plr_rownums are correct
for row in range(self.model.rowCount()):
assert row in self.model.playlist_rows
assert self.model.playlist_rows[row].plr_rownum == row
if row not in [2, 3, 4]:
assert self.model.playlist_rows[row].note == str(row)
elif row == 2:
assert self.model.playlist_rows[row].note == str(4)
elif row == 3:
assert self.model.playlist_rows[row].note == str(2)
elif row == 4:
assert self.model.playlist_rows[row].note == str(3)
def test_move_rows_test5(self):
# move rows [1, 4, 5, 10] → 8
self.model.move_rows([1, 4, 5, 10], 8)
# Check we have all rows and plr_rownums are correct
new_order = []
for row in range(self.model.rowCount()):
assert row in self.model.playlist_rows
assert self.model.playlist_rows[row].plr_rownum == row
new_order.append(int(self.model.playlist_rows[row].note))
assert new_order == [0, 2, 3, 6, 7, 8, 9, 1, 4, 5, 10]
def test_move_rows_test6(self):
# move rows [3, 6] → 5
self.model.move_rows([3, 6], 5)
# Check we have all rows and plr_rownums are correct
new_order = []
for row in range(self.model.rowCount()):
assert row in self.model.playlist_rows
assert self.model.playlist_rows[row].plr_rownum == row
new_order.append(int(self.model.playlist_rows[row].note))
assert new_order == [0, 1, 2, 4, 5, 3, 6, 7, 8, 9, 10]
def test_move_rows_test7(self):
# move rows [3, 5, 6] → 8
self.model.move_rows([3, 5, 6], 8)
# Check we have all rows and plr_rownums are correct
new_order = []
for row in range(self.model.rowCount()):
assert row in self.model.playlist_rows
assert self.model.playlist_rows[row].plr_rownum == row
new_order.append(int(self.model.playlist_rows[row].note))
assert new_order == [0, 1, 2, 4, 7, 8, 9, 10, 3, 5, 6]
def test_move_rows_test8(self):
# move rows [7, 8, 10] → 5
self.model.move_rows([7, 8, 10], 5)
# Check we have all rows and plr_rownums are correct
new_order = []
for row in range(self.model.rowCount()):
assert row in self.model.playlist_rows
assert self.model.playlist_rows[row].plr_rownum == row
new_order.append(int(self.model.playlist_rows[row].note))
assert new_order == [0, 1, 2, 3, 4, 7, 8, 10, 5, 6, 9]
def test_insert_header_row_end(self):
# insert header row at end of playlist
note_text = "test text"
assert self.model.rowCount() == self.ROWS_TO_CREATE
self.model.insert_row(proposed_row_number=None, note=note_text)
assert self.model.rowCount() == self.ROWS_TO_CREATE + 1
prd = self.model.playlist_rows[self.model.rowCount() - 1]
# Test against edit_role because display_role for headers is
# handled differently (sets up row span)
assert (
self.model.edit_role(
self.model.rowCount() - 1, playlistmodel.Col.NOTE.value, prd
)
== note_text
)
def test_insert_header_row_middle(self):
# insert header row in middle of playlist
note_text = "test text"
insert_row = 6
self.model.insert_row(proposed_row_number=insert_row, note=note_text)
assert self.model.rowCount() == self.ROWS_TO_CREATE + 1
prd = self.model.playlist_rows[insert_row]
# Test against edit_role because display_role for headers is
# handled differently (sets up row span)
assert (
self.model.edit_role(
self.model.rowCount() - 1, playlistmodel.Col.NOTE.value, prd
)
== note_text
)
def test_add_track_to_header(self):
note_text = "test text"
insert_row = 6
self.model.insert_row(proposed_row_number=insert_row, note=note_text)
assert self.model.rowCount() == self.ROWS_TO_CREATE + 1
prd = self.model.playlist_rows[1]
self.model.add_track_to_header(insert_row, prd.track_id)
def test_reverse_row_groups_one_row(self):
rows_to_move = [3]
result = self.model._reversed_contiguous_row_groups(rows_to_move)
assert len(result) == 1
assert result[0] == [3]
def test_reverse_row_groups_multiple_row(self):
rows_to_move = [2, 3, 4, 5, 7, 9, 10, 13, 17, 20, 21]
result = self.model._reversed_contiguous_row_groups(rows_to_move)
assert result == [[20, 21], [17], [13], [9, 10], [7], [2, 3, 4, 5]]
def test_move_one_row_between_playlists_to_end(self):
from_rows = [3]
to_row = self.ROWS_TO_CREATE
destination_playlist = "destination"
model_src = self.model
with db.Session() as session:
playlist_dst = Playlists(session, destination_playlist)
model_dst = playlistmodel.PlaylistModel(playlist_dst.id)
for row in range(self.ROWS_TO_CREATE):
model_dst.insert_row(proposed_row_number=row, note=str(row))
model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id)
model_dst.refresh_data(session)
assert len(model_src.playlist_rows) == self.ROWS_TO_CREATE - len(from_rows)
assert len(model_dst.playlist_rows) == self.ROWS_TO_CREATE + len(from_rows)
assert sorted([a.plr_rownum for a in model_src.playlist_rows.values()]) == list(
range(len(model_src.playlist_rows))
)
def test_move_one_row_between_playlists_to_middle(self):
from_rows = [3]
to_row = 2
destination_playlist = "destination"
model_src = self.model
with db.Session() as session:
playlist_dst = Playlists(session, destination_playlist)
model_dst = playlistmodel.PlaylistModel(playlist_dst.id)
for row in range(self.ROWS_TO_CREATE):
model_dst.insert_row(proposed_row_number=row, note=str(row))
model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id)
model_dst.refresh_data(session)
# Check the rows of the destination model
row_notes = []
for row_number in range(model_dst.rowCount()):
index = model_dst.index(
row_number, playlistmodel.Col.TITLE.value, QModelIndex()
)
row_notes.append(model_dst.data(index, Qt.ItemDataRole.EditRole).value())
assert len(model_src.playlist_rows) == self.ROWS_TO_CREATE - len(from_rows)
assert len(model_dst.playlist_rows) == self.ROWS_TO_CREATE + len(from_rows)
assert [int(a) for a in row_notes] == [0, 1, 3, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def test_move_multiple_rows_between_playlists_to_end(self):
from_rows = [1, 3, 4]
to_row = 2
destination_playlist = "destination"
model_src = self.model
with db.Session() as session:
playlist_dst = Playlists(session, destination_playlist)
model_dst = playlistmodel.PlaylistModel(playlist_dst.id)
for row in range(self.ROWS_TO_CREATE):
model_dst.insert_row(proposed_row_number=row, note=str(row))
model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id)
model_dst.refresh_data(session)
# Check the rows of the destination model
row_notes = []
for row_number in range(model_dst.rowCount()):
index = model_dst.index(
row_number, playlistmodel.Col.TITLE.value, QModelIndex()
)
row_notes.append(model_dst.data(index, Qt.ItemDataRole.EditRole).value())
# def test_move_rows_test3(monkeypatch, session):
# # move row 4 to row 3
# monkeypatch.setattr(playlistmodel, "Session", session)
# model = create_model_with_playlist_rows(session, 11)
# model.move_rows([4], 3)
# # Check we have all rows and plr_rownums are correct
# for row in range(model.rowCount()):
# assert row in model.playlist_rows
# assert model.playlist_rows[row].plr_rownum == row
# if row not in [3, 4]:
# assert model.playlist_rows[row].note == str(row)
# elif row == 3:
# assert model.playlist_rows[row].note == str(4)
# elif row == 4:
# assert model.playlist_rows[row].note == str(3)
# def test_move_rows_test4(monkeypatch, session):
# # move row 4 to row 2
# monkeypatch.setattr(playlistmodel, "Session", session)
# model = create_model_with_playlist_rows(session, 11)
# model.move_rows([4], 2)
# # Check we have all rows and plr_rownums are correct
# for row in range(model.rowCount()):
# assert row in model.playlist_rows
# assert model.playlist_rows[row].plr_rownum == row
# if row not in [2, 3, 4]:
# assert model.playlist_rows[row].note == str(row)
# elif row == 2:
# assert model.playlist_rows[row].note == str(4)
# elif row == 3:
# assert model.playlist_rows[row].note == str(2)
# elif row == 4:
# assert model.playlist_rows[row].note == str(3)
# def test_move_rows_test5(monkeypatch, session):
# # move rows [1, 4, 5, 10] → 8
# monkeypatch.setattr(playlistmodel, "Session", session)
# model = create_model_with_playlist_rows(session, 11)
# model.move_rows([1, 4, 5, 10], 8)
# # Check we have all rows and plr_rownums are correct
# new_order = []
# for row in range(model.rowCount()):
# assert row in model.playlist_rows
# assert model.playlist_rows[row].plr_rownum == row
# new_order.append(int(model.playlist_rows[row].note))
# assert new_order == [0, 2, 3, 6, 7, 8, 9, 1, 4, 5, 10]
# def test_move_rows_test6(monkeypatch, session):
# # move rows [3, 6] → 5
# monkeypatch.setattr(playlistmodel, "Session", session)
# model = create_model_with_playlist_rows(session, 11)
# model.move_rows([3, 6], 5)
# # Check we have all rows and plr_rownums are correct
# new_order = []
# for row in range(model.rowCount()):
# assert row in model.playlist_rows
# assert model.playlist_rows[row].plr_rownum == row
# new_order.append(int(model.playlist_rows[row].note))
# assert new_order == [0, 1, 2, 4, 5, 3, 6, 7, 8, 9, 10]
# def test_move_rows_test7(monkeypatch, session):
# # move rows [3, 5, 6] → 8
# monkeypatch.setattr(playlistmodel, "Session", session)
# model = create_model_with_playlist_rows(session, 11)
# model.move_rows([3, 5, 6], 8)
# # Check we have all rows and plr_rownums are correct
# new_order = []
# for row in range(model.rowCount()):
# assert row in model.playlist_rows
# assert model.playlist_rows[row].plr_rownum == row
# new_order.append(int(model.playlist_rows[row].note))
# assert new_order == [0, 1, 2, 4, 7, 8, 9, 10, 3, 5, 6]
# def test_move_rows_test8(monkeypatch, session):
# # move rows [7, 8, 10] → 5
# monkeypatch.setattr(playlistmodel, "Session", session)
# model = create_model_with_playlist_rows(session, 11)
# model.move_rows([7, 8, 10], 5)
# # Check we have all rows and plr_rownums are correct
# new_order = []
# for row in range(model.rowCount()):
# assert row in model.playlist_rows
# assert model.playlist_rows[row].plr_rownum == row
# new_order.append(int(model.playlist_rows[row].note))
# assert new_order == [0, 1, 2, 3, 4, 7, 8, 10, 5, 6, 9]
# def test_insert_header_row_end(monkeypatch, session):
# # insert header row at end of playlist
# monkeypatch.setattr(playlistmodel, "Session", session)
# note_text = "test text"
# initial_row_count = 11
# model = create_model_with_playlist_rows(session, initial_row_count)
# model.insert_row(proposed_row_number=None, note=note_text)
# assert model.rowCount() == initial_row_count + 1
# prd = model.playlist_rows[model.rowCount() - 1]
# # Test against edit_role because display_role for headers is
# # handled differently (sets up row span)
# assert (
# model.edit_role(model.rowCount() - 1, playlistmodel.Col.NOTE.value, prd)
# == note_text
# )
# def test_insert_header_row_middle(monkeypatch, session):
# # insert header row in middle of playlist
# monkeypatch.setattr(playlistmodel, "Session", session)
# note_text = "test text"
# initial_row_count = 11
# insert_row = 6
# model = create_model_with_playlist_rows(session, initial_row_count)
# model.insert_row(proposed_row_number=insert_row, note=note_text)
# assert model.rowCount() == initial_row_count + 1
# prd = model.playlist_rows[insert_row]
# # Test against edit_role because display_role for headers is
# # handled differently (sets up row span)
# assert (
# model.edit_role(model.rowCount() - 1, playlistmodel.Col.NOTE.value, prd)
# == note_text
# )
# def test_add_track_to_header(monkeypatch, session):
# monkeypatch.setattr(playlistmodel, "Session", session)
# note_text = "test text"
# initial_row_count = 11
# insert_row = 6
# model = create_model_with_playlist_rows(session, initial_row_count)
# model.insert_row(proposed_row_number=insert_row, note=note_text)
# assert model.rowCount() == initial_row_count + 1
# prd = model.playlist_rows[1]
# model.add_track_to_header(insert_row, prd.track_id)
# def test_create_model_with_tracks(monkeypatch, session):
# monkeypatch.setattr(playlistmodel, "Session", session)
# model = create_model_with_tracks(session)
# assert len(model.playlist_rows) == len(self.test_tracks)
# def test_timing_one_track(monkeypatch, session):
# START_ROW = 0
# END_ROW = 2
# monkeypatch.setattr(playlistmodel, "Session", session)
# model = create_model_with_tracks(session)
# model.insert_row(proposed_row_number=START_ROW, note="start+")
# model.insert_row(proposed_row_number=END_ROW, note="-")
# prd = model.playlist_rows[START_ROW]
# qv_value = model.display_role(START_ROW, playlistmodel.HEADER_NOTES_COLUMN, prd)
# assert qv_value.value() == "start [1 tracks, 4:23 unplayed]"
# def test_insert_track_new_playlist(monkeypatch, session):
# # insert a track into a new playlist
# monkeypatch.setattr(playlistmodel, "Session", session)
# playlist = Playlists(session, "test playlist")
# # Create a model
# model = playlistmodel.PlaylistModel(playlist.id)
# track_path = self.test_tracks[0]
# metadata = get_file_metadata(track_path)
# track = Tracks(session, **metadata)
# model.insert_row(proposed_row_number=0, track_id=track.id)
# prd = model.playlist_rows[model.rowCount() - 1]
# assert (
# model.edit_role(model.rowCount() - 1, playlistmodel.Col.TITLE.value, prd)
# == metadata["title"]
# )
# def test_reverse_row_groups_one_row(monkeypatch, session):
# monkeypatch.setattr(playlistmodel, "Session", session)
# rows_to_move = [3]
# model_src = create_model_with_playlist_rows(session, 5, name="source")
# result = model_src._reversed_contiguous_row_groups(rows_to_move)
# assert len(result) == 1
# assert result[0] == [3]
# def test_reverse_row_groups_multiple_row(monkeypatch, session):
# monkeypatch.setattr(playlistmodel, "Session", session)
# rows_to_move = [2, 3, 4, 5, 7, 9, 10, 13, 17, 20, 21]
# model_src = create_model_with_playlist_rows(session, 5, name="source")
# result = model_src._reversed_contiguous_row_groups(rows_to_move)
# assert result == [[20, 21], [17], [13], [9, 10], [7], [2, 3, 4, 5]]
# def test_move_one_row_between_playlists_to_end(monkeypatch, session):
# monkeypatch.setattr(playlistmodel, "Session", session)
# create_rowcount = 5
# from_rows = [3]
# to_row = create_rowcount
# model_src = create_model_with_playlist_rows(session, create_rowcount, name="source")
# model_dst = create_model_with_playlist_rows(
# session, create_rowcount, name="destination"
# )
# model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id)
# model_dst.refresh_data(session)
# assert len(model_src.playlist_rows) == create_rowcount - len(from_rows)
# assert len(model_dst.playlist_rows) == create_rowcount + len(from_rows)
# assert sorted([a.plr_rownum for a in model_src.playlist_rows.values()]) == list(
# range(len(model_src.playlist_rows))
# )
# def test_move_one_row_between_playlists_to_middle(monkeypatch, session):
# monkeypatch.setattr(playlistmodel, "Session", session)
# create_rowcount = 5
# from_rows = [3]
# to_row = 2
# model_src = create_model_with_playlist_rows(session, create_rowcount, name="source")
# model_dst = create_model_with_playlist_rows(
# session, create_rowcount, name="destination"
# )
# model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id)
# model_dst.refresh_data(session)
# # Check the rows of the destination model
# row_notes = []
# for row_number in range(model_dst.rowCount()):
# index = model_dst.index(
# row_number, playlistmodel.Col.TITLE.value, QModelIndex()
# )
# row_notes.append(model_dst.data(index, Qt.ItemDataRole.EditRole).value())
# assert len(model_src.playlist_rows) == create_rowcount - len(from_rows)
# assert len(model_dst.playlist_rows) == create_rowcount + len(from_rows)
# assert [int(a) for a in row_notes] == [0, 1, 3, 2, 3, 4]
# def test_move_multiple_rows_between_playlists_to_end(monkeypatch, session):
# monkeypatch.setattr(playlistmodel, "Session", session)
# create_rowcount = 5
# from_rows = [1, 3, 4]
# to_row = 2
# model_src = create_model_with_playlist_rows(session, create_rowcount, name="source")
# model_dst = create_model_with_playlist_rows(
# session, create_rowcount, name="destination"
# )
# model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id)
# model_dst.refresh_data(session)
# # Check the rows of the destination model
# row_notes = []
# for row_number in range(model_dst.rowCount()):
# index = model_dst.index(
# row_number, playlistmodel.Col.TITLE.value, QModelIndex()
# )
# row_notes.append(model_dst.data(index, Qt.ItemDataRole.EditRole).value())
# assert len(model_src.playlist_rows) == create_rowcount - len(from_rows)
# assert len(model_dst.playlist_rows) == create_rowcount + len(from_rows)
# assert [int(a) for a in row_notes] == [0, 1, 3, 4, 1, 2, 3, 4]
assert len(model_src.playlist_rows) == self.ROWS_TO_CREATE - len(from_rows)
assert len(model_dst.playlist_rows) == self.ROWS_TO_CREATE + len(from_rows)
assert [int(a) for a in row_notes] == [0, 1, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# # def test_edit_header(monkeypatch, session): # edit header row in middle of playlist