Use model(s) to save/restore current play state
This commit is contained in:
parent
44841710e0
commit
a5a9178b06
@ -180,6 +180,7 @@ class Core(
|
|||||||
file_name = os.path.join(
|
file_name = os.path.join(
|
||||||
self._config['core']['data_dir'], name)
|
self._config['core']['data_dir'], name)
|
||||||
file_name += '.state'
|
file_name += '.state'
|
||||||
|
logger.info('Save state to "%s"', file_name)
|
||||||
|
|
||||||
data = {}
|
data = {}
|
||||||
self.tracklist._state_export(data)
|
self.tracklist._state_export(data)
|
||||||
|
|||||||
@ -60,13 +60,17 @@ class HistoryController(object):
|
|||||||
|
|
||||||
def _state_export(self, data):
|
def _state_export(self, data):
|
||||||
"""Internal method for :class:`mopidy.Core`."""
|
"""Internal method for :class:`mopidy.Core`."""
|
||||||
data['history'] = {}
|
history_list = []
|
||||||
data['history']['history'] = self._history
|
for timestamp, track in self._history:
|
||||||
|
history_list.append(models.HistoryTrack(
|
||||||
|
timestamp=timestamp, track=track))
|
||||||
|
data['history'] = models.HistoryState(history=history_list)
|
||||||
|
|
||||||
def _state_import(self, data, coverage):
|
def _state_import(self, data, coverage):
|
||||||
"""Internal method for :class:`mopidy.Core`."""
|
"""Internal method for :class:`mopidy.Core`."""
|
||||||
if 'history' not in data:
|
if 'history' in data:
|
||||||
return
|
hstate = data['history']
|
||||||
if 'history' in coverage:
|
if 'history' in coverage:
|
||||||
if 'history' in data['history']:
|
self._history = []
|
||||||
self._history = data['history']['history']
|
for htrack in hstate.history:
|
||||||
|
self._history.append((htrack.timestamp, htrack.track))
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import logging
|
|||||||
|
|
||||||
from mopidy import exceptions
|
from mopidy import exceptions
|
||||||
from mopidy.internal import validation
|
from mopidy.internal import validation
|
||||||
|
from mopidy.models import MixerState
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -102,13 +103,11 @@ class MixerController(object):
|
|||||||
|
|
||||||
def _state_export(self, data):
|
def _state_export(self, data):
|
||||||
"""Internal method for :class:`mopidy.Core`."""
|
"""Internal method for :class:`mopidy.Core`."""
|
||||||
data['mixer'] = {}
|
data['mixer'] = MixerState(volume=self.get_volume())
|
||||||
data['mixer']['volume'] = self.get_volume()
|
|
||||||
|
|
||||||
def _state_import(self, data, coverage):
|
def _state_import(self, data, coverage):
|
||||||
"""Internal method for :class:`mopidy.Core`."""
|
"""Internal method for :class:`mopidy.Core`."""
|
||||||
if 'mixer' not in data:
|
if 'mixer' in data:
|
||||||
return
|
ms = data['mixer']
|
||||||
if 'volume' in coverage:
|
if 'volume' in coverage:
|
||||||
if 'volume' in data['mixer']:
|
self.set_volume(ms.volume)
|
||||||
self.set_volume(data['mixer']['volume'])
|
|
||||||
|
|||||||
@ -526,20 +526,18 @@ class PlaybackController(object):
|
|||||||
|
|
||||||
def _state_export(self, data):
|
def _state_export(self, data):
|
||||||
"""Internal method for :class:`mopidy.Core`."""
|
"""Internal method for :class:`mopidy.Core`."""
|
||||||
data['playback'] = {}
|
data['playback'] = models.PlaybackState(
|
||||||
data['playback']['current_tl_track'] = self.get_current_tl_track()
|
tl_track=self.get_current_tl_track(),
|
||||||
data['playback']['position'] = self.get_time_position()
|
position=self.get_time_position(),
|
||||||
# TODO: export/import get_state()?
|
state=self.get_state())
|
||||||
|
|
||||||
def _state_import(self, data, coverage):
|
def _state_import(self, data, coverage):
|
||||||
"""Internal method for :class:`mopidy.Core`."""
|
"""Internal method for :class:`mopidy.Core`."""
|
||||||
if 'playback' not in data:
|
if 'playback' in data:
|
||||||
return
|
ps = data['playback']
|
||||||
if 'autoplay' in coverage:
|
if 'autoplay' in coverage:
|
||||||
if 'current_tl_track' in data['playback']:
|
tl_track = ps.tl_track
|
||||||
tl_track = data['playback']['current_tl_track']
|
|
||||||
if tl_track is not None:
|
if tl_track is not None:
|
||||||
self.play(tl_track=tl_track)
|
self.play(tl_track=tl_track)
|
||||||
# TODO: Seek not working. It seeks to early.
|
# TODO: Seek not working. It seeks to early.
|
||||||
# if 'position' in data['playback']:
|
# self.seek(ps.position)
|
||||||
# self.seek(data['playback']['position'])
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import random
|
|||||||
from mopidy import exceptions
|
from mopidy import exceptions
|
||||||
from mopidy.core import listener
|
from mopidy.core import listener
|
||||||
from mopidy.internal import deprecation, validation
|
from mopidy.internal import deprecation, validation
|
||||||
from mopidy.models import TlTrack, Track
|
from mopidy.models import TlTrack, Track, TracklistState
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -647,32 +647,27 @@ class TracklistController(object):
|
|||||||
|
|
||||||
def _state_export(self, data):
|
def _state_export(self, data):
|
||||||
"""Internal method for :class:`mopidy.Core`."""
|
"""Internal method for :class:`mopidy.Core`."""
|
||||||
data['tracklist'] = {}
|
data['tracklist'] = TracklistState(
|
||||||
data['tracklist']['tl_tracks'] = self._tl_tracks
|
tracks=self._tl_tracks,
|
||||||
data['tracklist']['next_tlid'] = self._next_tlid
|
next_tlid=self._next_tlid,
|
||||||
data['tracklist']['consume'] = self.get_consume()
|
consume=self.get_consume(),
|
||||||
data['tracklist']['random'] = self.get_random()
|
random=self.get_random(),
|
||||||
data['tracklist']['repeat'] = self.get_repeat()
|
repeat=self.get_repeat(),
|
||||||
data['tracklist']['single'] = self.get_single()
|
single=self.get_single())
|
||||||
|
|
||||||
def _state_import(self, data, coverage):
|
def _state_import(self, data, coverage):
|
||||||
"""Internal method for :class:`mopidy.Core`."""
|
"""Internal method for :class:`mopidy.Core`."""
|
||||||
if 'tracklist' not in data:
|
if 'tracklist' in data:
|
||||||
return
|
tls = data['tracklist']
|
||||||
if 'mode' in coverage:
|
if 'mode' in coverage:
|
||||||
# TODO: only one _trigger_options_changed() for all options
|
self.set_consume(tls.consume)
|
||||||
if 'consume' in data['tracklist']:
|
self.set_random(tls.random)
|
||||||
self.set_consume(data['tracklist']['consume'])
|
self.set_repeat(tls.repeat)
|
||||||
if 'random' in data['tracklist']:
|
self.set_single(tls.single)
|
||||||
self.set_random(data['tracklist']['random'])
|
if 'tracklist' in coverage:
|
||||||
if 'repeat' in data['tracklist']:
|
if tls.next_tlid > self._next_tlid:
|
||||||
self.set_repeat(data['tracklist']['repeat'])
|
self._next_tlid = tls.next_tlid
|
||||||
if 'single' in data['tracklist']:
|
self._tl_tracks = []
|
||||||
self.set_single(data['tracklist']['single'])
|
for track in tls.tracks:
|
||||||
if 'tracklist' in coverage:
|
self._tl_tracks.append(track)
|
||||||
if 'next_tlid' in data['tracklist']:
|
|
||||||
if data['tracklist']['next_tlid'] > self._next_tlid:
|
|
||||||
self._next_tlid = data['tracklist']['next_tlid']
|
|
||||||
if 'tl_tracks' in data['tracklist']:
|
|
||||||
self._tl_tracks = data['tracklist']['tl_tracks']
|
|
||||||
self._trigger_tracklist_changed()
|
self._trigger_tracklist_changed()
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
from mopidy import compat
|
from mopidy import compat
|
||||||
|
from mopidy.internal import validation
|
||||||
from mopidy.models import fields
|
from mopidy.models import fields
|
||||||
from mopidy.models.immutable import ImmutableObject, ValidatedImmutableObject
|
from mopidy.models.immutable import ImmutableObject, ValidatedImmutableObject
|
||||||
from mopidy.models.serialize import ModelJSONEncoder, model_json_decoder
|
from mopidy.models.serialize import ModelJSONEncoder, model_json_decoder
|
||||||
@ -8,7 +9,8 @@ from mopidy.models.serialize import ModelJSONEncoder, model_json_decoder
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
'ImmutableObject', 'Ref', 'Image', 'Artist', 'Album', 'track', 'TlTrack',
|
'ImmutableObject', 'Ref', 'Image', 'Artist', 'Album', 'track', 'TlTrack',
|
||||||
'Playlist', 'SearchResult', 'model_json_decoder', 'ModelJSONEncoder',
|
'Playlist', 'SearchResult', 'model_json_decoder', 'ModelJSONEncoder',
|
||||||
'ValidatedImmutableObject']
|
'ValidatedImmutableObject', 'HistoryTrack', 'HistoryState', 'MixerState',
|
||||||
|
'PlaybackState', 'TracklistState']
|
||||||
|
|
||||||
|
|
||||||
class Ref(ValidatedImmutableObject):
|
class Ref(ValidatedImmutableObject):
|
||||||
@ -360,3 +362,104 @@ class SearchResult(ValidatedImmutableObject):
|
|||||||
|
|
||||||
# The albums matching the search query. Read-only.
|
# The albums matching the search query. Read-only.
|
||||||
albums = fields.Collection(type=Album, container=tuple)
|
albums = fields.Collection(type=Album, container=tuple)
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryTrack(ValidatedImmutableObject):
|
||||||
|
"""
|
||||||
|
A history track. Wraps a :class:`Ref` and it's timestamp.
|
||||||
|
|
||||||
|
:param timestamp: the timestamp
|
||||||
|
:type timestamp: int
|
||||||
|
:param track: the track
|
||||||
|
:type track: :class:`Ref`
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The timestamp. Read-only.
|
||||||
|
timestamp = fields.Integer()
|
||||||
|
|
||||||
|
# The track. Read-only.
|
||||||
|
track = fields.Field(type=Ref)
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryState(ValidatedImmutableObject):
|
||||||
|
"""
|
||||||
|
State of the history controller.
|
||||||
|
Internally used for import/export of current state.
|
||||||
|
|
||||||
|
:param history: the track history
|
||||||
|
:type history: list of :class:`HistoryTrack`
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The tracks. Read-only.
|
||||||
|
history = fields.Collection(type=HistoryTrack, container=tuple)
|
||||||
|
|
||||||
|
|
||||||
|
class MixerState(ValidatedImmutableObject):
|
||||||
|
"""
|
||||||
|
State of the mixer controller.
|
||||||
|
Internally used for import/export of current state.
|
||||||
|
|
||||||
|
:param volume: the volume
|
||||||
|
:type volume: int
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The volume. Read-only.
|
||||||
|
volume = fields.Integer(min=0, max=100)
|
||||||
|
|
||||||
|
|
||||||
|
class PlaybackState(ValidatedImmutableObject):
|
||||||
|
"""
|
||||||
|
State of the playback controller.
|
||||||
|
Internally used for import/export of current state.
|
||||||
|
|
||||||
|
:param tl_track: current track
|
||||||
|
:type tl_track: :class:`TlTrack`
|
||||||
|
:param position: play position
|
||||||
|
:type position: int
|
||||||
|
:param state: playback state
|
||||||
|
:type state: :class:`TlTrack`
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The current playing track. Read-only.
|
||||||
|
tl_track = fields.Field(type=TlTrack)
|
||||||
|
|
||||||
|
# The playback position. Read-only.
|
||||||
|
position = fields.Integer(min=0)
|
||||||
|
|
||||||
|
# The playback state. Read-only.
|
||||||
|
state = fields.Field(choices=validation.PLAYBACK_STATES)
|
||||||
|
|
||||||
|
|
||||||
|
class TracklistState(ValidatedImmutableObject):
|
||||||
|
|
||||||
|
"""
|
||||||
|
State of the tracklist controller.
|
||||||
|
Internally used for import/export of current state.
|
||||||
|
|
||||||
|
:param repeat: the repeat mode
|
||||||
|
:type repeat: bool
|
||||||
|
:param consume: the consume mode
|
||||||
|
:type consume: bool
|
||||||
|
:param random: the random mode
|
||||||
|
:type random: bool
|
||||||
|
:param single: the single mode
|
||||||
|
:type single: bool
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The repeat mode. Read-only.
|
||||||
|
repeat = fields.Boolean()
|
||||||
|
|
||||||
|
# The consume mode. Read-only.
|
||||||
|
consume = fields.Boolean()
|
||||||
|
|
||||||
|
# The random mode. Read-only.
|
||||||
|
random = fields.Boolean()
|
||||||
|
|
||||||
|
# The single mode. Read-only.
|
||||||
|
single = fields.Boolean()
|
||||||
|
|
||||||
|
# The repeat mode. Read-only.
|
||||||
|
next_tlid = fields.Integer(min=0)
|
||||||
|
|
||||||
|
# The list of tracks. Read-only.
|
||||||
|
tracks = fields.Collection(type=TlTrack, container=tuple)
|
||||||
|
|||||||
@ -135,6 +135,17 @@ class Integer(Field):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class Boolean(Field):
|
||||||
|
"""
|
||||||
|
:class:`Field` for storing boolean values
|
||||||
|
|
||||||
|
:param default: default value for field
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, default=None):
|
||||||
|
super(Boolean, self).__init__(type=bool, default=default)
|
||||||
|
|
||||||
|
|
||||||
class Collection(Field):
|
class Collection(Field):
|
||||||
"""
|
"""
|
||||||
:class:`Field` for storing collections of a given type.
|
:class:`Field` for storing collections of a given type.
|
||||||
|
|||||||
@ -4,7 +4,9 @@ import json
|
|||||||
|
|
||||||
from mopidy.models import immutable
|
from mopidy.models import immutable
|
||||||
|
|
||||||
_MODELS = ['Ref', 'Artist', 'Album', 'Track', 'TlTrack', 'Playlist']
|
_MODELS = ['Ref', 'Artist', 'Album', 'Track', 'TlTrack', 'Playlist',
|
||||||
|
'HistoryTrack', 'HistoryState', 'MixerState', 'PlaybackState',
|
||||||
|
'TracklistState']
|
||||||
|
|
||||||
|
|
||||||
class ModelJSONEncoder(json.JSONEncoder):
|
class ModelJSONEncoder(json.JSONEncoder):
|
||||||
|
|||||||
@ -173,6 +173,27 @@ class IntegerTest(unittest.TestCase):
|
|||||||
instance.attr = 11
|
instance.attr = 11
|
||||||
|
|
||||||
|
|
||||||
|
class BooleanTest(unittest.TestCase):
|
||||||
|
def test_default_handling(self):
|
||||||
|
instance = create_instance(Boolean(default=True))
|
||||||
|
self.assertEqual(True, instance.attr)
|
||||||
|
|
||||||
|
def test_true_allowed(self):
|
||||||
|
instance = create_instance(Boolean())
|
||||||
|
instance.attr = True
|
||||||
|
self.assertEqual(True, instance.attr)
|
||||||
|
|
||||||
|
def test_false_allowed(self):
|
||||||
|
instance = create_instance(Boolean())
|
||||||
|
instance.attr = False
|
||||||
|
self.assertEqual(False, instance.attr)
|
||||||
|
|
||||||
|
def test_int_forbidden(self):
|
||||||
|
instance = create_instance(Boolean())
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
instance.attr = 1
|
||||||
|
|
||||||
|
|
||||||
class CollectionTest(unittest.TestCase):
|
class CollectionTest(unittest.TestCase):
|
||||||
def test_container_instance_is_default(self):
|
def test_container_instance_is_default(self):
|
||||||
instance = create_instance(Collection(type=int, container=frozenset))
|
instance = create_instance(Collection(type=int, container=frozenset))
|
||||||
|
|||||||
@ -4,8 +4,9 @@ import json
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from mopidy.models import (
|
from mopidy.models import (
|
||||||
Album, Artist, Image, ModelJSONEncoder, Playlist, Ref, SearchResult,
|
Album, Artist, HistoryState, HistoryTrack, Image, MixerState,
|
||||||
TlTrack, Track, model_json_decoder)
|
ModelJSONEncoder, PlaybackState, Playlist,
|
||||||
|
Ref, SearchResult, TlTrack, Track, TracklistState, model_json_decoder)
|
||||||
|
|
||||||
|
|
||||||
class InheritanceTest(unittest.TestCase):
|
class InheritanceTest(unittest.TestCase):
|
||||||
@ -1168,3 +1169,164 @@ class SearchResultTest(unittest.TestCase):
|
|||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
{'__model__': 'SearchResult', 'uri': 'uri'},
|
{'__model__': 'SearchResult', 'uri': 'uri'},
|
||||||
SearchResult(uri='uri').serialize())
|
SearchResult(uri='uri').serialize())
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryTrackTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_track(self):
|
||||||
|
track = Ref.track()
|
||||||
|
result = HistoryTrack(track=track)
|
||||||
|
self.assertEqual(result.track, track)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.track = None
|
||||||
|
|
||||||
|
def test_timestamp(self):
|
||||||
|
timestamp = 1234
|
||||||
|
result = HistoryTrack(timestamp=timestamp)
|
||||||
|
self.assertEqual(result.timestamp, timestamp)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.timestamp = None
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryStateTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_history_list(self):
|
||||||
|
history = (HistoryTrack(),
|
||||||
|
HistoryTrack())
|
||||||
|
result = HistoryState(history=history)
|
||||||
|
self.assertEqual(result.history, history)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.history = None
|
||||||
|
|
||||||
|
def test_history_string_fail(self):
|
||||||
|
history = 'not_a_valid_history'
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
HistoryState(history=history)
|
||||||
|
|
||||||
|
|
||||||
|
class MixerStateTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_volume(self):
|
||||||
|
volume = 37
|
||||||
|
result = MixerState(volume=volume)
|
||||||
|
self.assertEqual(result.volume, volume)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.volume = None
|
||||||
|
|
||||||
|
def test_volume_invalid(self):
|
||||||
|
volume = 105
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
MixerState(volume=volume)
|
||||||
|
|
||||||
|
|
||||||
|
class PlaybackStateTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_position(self):
|
||||||
|
position = 123456
|
||||||
|
result = PlaybackState(position=position)
|
||||||
|
self.assertEqual(result.position, position)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.position = None
|
||||||
|
|
||||||
|
def test_position_invalid(self):
|
||||||
|
position = -1
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
PlaybackState(position=position)
|
||||||
|
|
||||||
|
def test_tl_track(self):
|
||||||
|
tl_track = TlTrack()
|
||||||
|
result = PlaybackState(tl_track=tl_track)
|
||||||
|
self.assertEqual(result.tl_track, tl_track)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.tl_track = None
|
||||||
|
|
||||||
|
def test_tl_track_none(self):
|
||||||
|
tl_track = None
|
||||||
|
result = PlaybackState(tl_track=tl_track)
|
||||||
|
self.assertEqual(result.tl_track, tl_track)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.tl_track = None
|
||||||
|
|
||||||
|
def test_tl_track_invalid(self):
|
||||||
|
tl_track = Track()
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
PlaybackState(tl_track=tl_track)
|
||||||
|
|
||||||
|
def test_state(self):
|
||||||
|
state = 'playing'
|
||||||
|
result = PlaybackState(state=state)
|
||||||
|
self.assertEqual(result.state, state)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.state = None
|
||||||
|
|
||||||
|
def test_state_invalid(self):
|
||||||
|
state = 'not_a_state'
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
PlaybackState(state=state)
|
||||||
|
|
||||||
|
|
||||||
|
class TracklistStateTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_repeat_true(self):
|
||||||
|
repeat = True
|
||||||
|
result = TracklistState(repeat=repeat)
|
||||||
|
self.assertEqual(result.repeat, repeat)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.repeat = None
|
||||||
|
|
||||||
|
def test_repeat_false(self):
|
||||||
|
repeat = False
|
||||||
|
result = TracklistState(repeat=repeat)
|
||||||
|
self.assertEqual(result.repeat, repeat)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.repeat = None
|
||||||
|
|
||||||
|
def test_repeat_invalid(self):
|
||||||
|
repeat = 33
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
TracklistState(repeat=repeat)
|
||||||
|
|
||||||
|
def test_consume_true(self):
|
||||||
|
val = True
|
||||||
|
result = TracklistState(consume=val)
|
||||||
|
self.assertEqual(result.consume, val)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.repeat = None
|
||||||
|
|
||||||
|
def test_random_true(self):
|
||||||
|
val = True
|
||||||
|
result = TracklistState(random=val)
|
||||||
|
self.assertEqual(result.random, val)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.random = None
|
||||||
|
|
||||||
|
def test_single_true(self):
|
||||||
|
val = True
|
||||||
|
result = TracklistState(single=val)
|
||||||
|
self.assertEqual(result.single, val)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.single = None
|
||||||
|
|
||||||
|
def test_next_tlid(self):
|
||||||
|
val = 654
|
||||||
|
result = TracklistState(next_tlid=val)
|
||||||
|
self.assertEqual(result.next_tlid, val)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.next_tlid = None
|
||||||
|
|
||||||
|
def test_next_tlid_invalid(self):
|
||||||
|
val = -1
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
TracklistState(next_tlid=val)
|
||||||
|
|
||||||
|
def test_tracks(self):
|
||||||
|
tracks = (TlTrack(), TlTrack())
|
||||||
|
result = TracklistState(tracks=tracks)
|
||||||
|
self.assertEqual(result.tracks, tracks)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
result.tracks = None
|
||||||
|
|
||||||
|
def test_tracks_invalid(self):
|
||||||
|
tracks = (Track(), Track())
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
TracklistState(tracks=tracks)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user