Merge pull request #1115 from ostcar/projector
Fixed countdown and projector update issues
This commit is contained in:
commit
4af1d32dd1
@ -128,16 +128,21 @@ class Item(SlideMixin, MPTTModel):
|
|||||||
The link can be detail, update or delete.
|
The link can be detail, update or delete.
|
||||||
"""
|
"""
|
||||||
if link == 'detail' or link == 'view':
|
if link == 'detail' or link == 'view':
|
||||||
return reverse('item_view', args=[str(self.id)])
|
url = reverse('item_view', args=[str(self.id)])
|
||||||
if link == 'update' or link == 'edit':
|
elif link == 'update' or link == 'edit':
|
||||||
return reverse('item_edit', args=[str(self.id)])
|
url = reverse('item_edit', args=[str(self.id)])
|
||||||
if link == 'delete':
|
elif link == 'delete':
|
||||||
return reverse('item_delete', args=[str(self.id)])
|
url = reverse('item_delete', args=[str(self.id)])
|
||||||
if link == 'projector_list_of_speakers':
|
elif link == 'projector_list_of_speakers':
|
||||||
return '%s&type=list_of_speakers' % super(Item, self).get_absolute_url('projector')
|
url = '%s&type=list_of_speakers' % super(Item, self).get_absolute_url('projector')
|
||||||
if link == 'projector_summary':
|
elif link == 'projector_summary':
|
||||||
return '%s&type=summary' % super(Item, self).get_absolute_url('projector')
|
url = '%s&type=summary' % super(Item, self).get_absolute_url('projector')
|
||||||
return super(Item, self).get_absolute_url(link)
|
elif (link in ('projector', 'projector_preview') and
|
||||||
|
self.content_object and isinstance(self.content_object, SlideMixin)):
|
||||||
|
url = self.content_object.get_absolute_url(link)
|
||||||
|
else:
|
||||||
|
url = super(Item, self).get_absolute_url(link)
|
||||||
|
return url
|
||||||
|
|
||||||
def get_title(self):
|
def get_title(self):
|
||||||
"""
|
"""
|
||||||
@ -268,6 +273,17 @@ class Item(SlideMixin, MPTTModel):
|
|||||||
# The list of speakers is empty.
|
# The list of speakers is empty.
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def is_active_slide(self):
|
||||||
|
"""
|
||||||
|
Returns True if the slide is True. If the slide is a related item,
|
||||||
|
Returns True if the related object is active.
|
||||||
|
"""
|
||||||
|
if self.content_object and isinstance(self.content_object, SlideMixin):
|
||||||
|
value = self.content_object.is_active_slide()
|
||||||
|
else:
|
||||||
|
value = super(Item, self).is_active_slide()
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class SpeakerManager(models.Manager):
|
class SpeakerManager(models.Manager):
|
||||||
def add(self, person, item):
|
def add(self, person, item):
|
||||||
@ -373,6 +389,10 @@ class Speaker(models.Model):
|
|||||||
if config['agenda_couple_countdown_and_speakers']:
|
if config['agenda_couple_countdown_and_speakers']:
|
||||||
reset_countdown()
|
reset_countdown()
|
||||||
start_countdown()
|
start_countdown()
|
||||||
|
if self.item.is_active_slide():
|
||||||
|
# TODO: only update the overlay if the overlay is active and
|
||||||
|
# slide type is None.
|
||||||
|
update_projector_overlay('projector_countdown')
|
||||||
|
|
||||||
def end_speach(self):
|
def end_speach(self):
|
||||||
"""
|
"""
|
||||||
@ -383,3 +403,7 @@ class Speaker(models.Model):
|
|||||||
# stop countdown
|
# stop countdown
|
||||||
if config['agenda_couple_countdown_and_speakers']:
|
if config['agenda_couple_countdown_and_speakers']:
|
||||||
stop_countdown()
|
stop_countdown()
|
||||||
|
if self.item.is_active_slide():
|
||||||
|
# TODO: only update the overlay if the overlay is active and
|
||||||
|
# slide type is None.
|
||||||
|
update_projector_overlay('projector_countdown')
|
||||||
|
@ -172,14 +172,13 @@ def register_slide_model(SlideModel, template):
|
|||||||
register_slide(SlideModel.slide_callback_name, model_slide)
|
register_slide(SlideModel.slide_callback_name, model_slide)
|
||||||
|
|
||||||
|
|
||||||
def set_active_slide(callback, kwargs=None):
|
def set_active_slide(callback, **kwargs):
|
||||||
"""
|
"""
|
||||||
Set the active Slide.
|
Set the active Slide.
|
||||||
|
|
||||||
callback: The name of the slide callback.
|
callback: The name of the slide callback.
|
||||||
kwargs: Keyword arguments for the slide callback.
|
kwargs: Keyword arguments for the slide callback.
|
||||||
"""
|
"""
|
||||||
kwargs = kwargs or {}
|
|
||||||
kwargs.update(callback=callback)
|
kwargs.update(callback=callback)
|
||||||
config['projector_active_slide'] = kwargs
|
config['projector_active_slide'] = kwargs
|
||||||
update_projector()
|
update_projector()
|
||||||
|
@ -5,6 +5,7 @@ from django.contrib.staticfiles.templatetags.staticfiles import static
|
|||||||
from django.core.context_processors import csrf
|
from django.core.context_processors import csrf
|
||||||
from django.dispatch import receiver, Signal
|
from django.dispatch import receiver, Signal
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
from django.utils.datastructures import SortedDict
|
||||||
|
|
||||||
from openslides.config.api import config, ConfigPage, ConfigVariable
|
from openslides.config.api import config, ConfigPage, ConfigVariable
|
||||||
from openslides.config.signals import config_signal
|
from openslides.config.signals import config_signal
|
||||||
@ -99,18 +100,14 @@ def countdown(sender, **kwargs):
|
|||||||
"""
|
"""
|
||||||
Returns JavaScript for the projector
|
Returns JavaScript for the projector
|
||||||
"""
|
"""
|
||||||
start = int(config['countdown_start_stamp'])
|
value = SortedDict()
|
||||||
duration = int(config['countdown_time'])
|
value['load_file'] = static('javascript/countdown.js')
|
||||||
pause = int(config['countdown_pause_stamp'])
|
value['projector_countdown_start'] = int(config['countdown_start_stamp'])
|
||||||
state = config['countdown_state']
|
value['projector_countdown_duration'] = int(config['countdown_time'])
|
||||||
|
value['projector_countdown_pause'] = int(config['countdown_pause_stamp'])
|
||||||
return {
|
value['projector_countdown_state'] = config['countdown_state']
|
||||||
'load_file': static('javascript/countdown.js'),
|
value['call'] = 'update_countdown();'
|
||||||
'call': 'update_countdown();',
|
return value
|
||||||
'projector_countdown_start': start,
|
|
||||||
'projector_countdown_duration': duration,
|
|
||||||
'projector_countdown_pause': pause,
|
|
||||||
'projector_countdown_state': state}
|
|
||||||
|
|
||||||
def get_projector_html():
|
def get_projector_html():
|
||||||
"""
|
"""
|
||||||
|
@ -85,7 +85,7 @@ class ActivateView(RedirectView):
|
|||||||
ProjectorSocketHandler.send_updates(
|
ProjectorSocketHandler.send_updates(
|
||||||
{'calls': {'load_pdf': {'url': url, 'page_num': kwargs['page_num']}}})
|
{'calls': {'load_pdf': {'url': url, 'page_num': kwargs['page_num']}}})
|
||||||
else:
|
else:
|
||||||
set_active_slide(kwargs['callback'], kwargs=dict(request.GET.items()))
|
set_active_slide(kwargs['callback'], **dict(request.GET.items()))
|
||||||
config['projector_scroll'] = config.get_default('projector_scroll')
|
config['projector_scroll'] = config.get_default('projector_scroll')
|
||||||
config['projector_scale'] = config.get_default('projector_scale')
|
config['projector_scale'] = config.get_default('projector_scale')
|
||||||
call_on_projector({'scroll': config['projector_scroll'],
|
call_on_projector({'scroll': config['projector_scroll'],
|
||||||
@ -186,10 +186,10 @@ class CountdownControllView(RedirectView):
|
|||||||
try:
|
try:
|
||||||
config['countdown_time'] = \
|
config['countdown_time'] = \
|
||||||
int(self.request.GET['countdown_time'])
|
int(self.request.GET['countdown_time'])
|
||||||
except ValueError:
|
except (ValueError, AttributeError):
|
||||||
pass
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
pass
|
||||||
|
else:
|
||||||
|
reset_countdown()
|
||||||
update_projector_overlay('projector_countdown')
|
update_projector_overlay('projector_countdown')
|
||||||
|
|
||||||
def get_ajax_context(self, **kwargs):
|
def get_ajax_context(self, **kwargs):
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
from openslides.projector.models import SlideMixin
|
||||||
|
|
||||||
class RelatedItem(models.Model):
|
|
||||||
|
class RelatedItem(SlideMixin, models.Model):
|
||||||
|
slide_callback_name = 'test_related_item'
|
||||||
name = models.CharField(max_length='255')
|
name = models.CharField(max_length='255')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -13,8 +18,12 @@ class RelatedItem(models.Model):
|
|||||||
def get_agenda_title_supplement(self):
|
def get_agenda_title_supplement(self):
|
||||||
return 'test item'
|
return 'test item'
|
||||||
|
|
||||||
def get_absolute_url(self, *args, **kwargs):
|
def get_absolute_url(self, link=None):
|
||||||
return '/absolute-url-here/'
|
if link is None:
|
||||||
|
value = '/absolute-url-here/'
|
||||||
|
else:
|
||||||
|
value = super(RelatedItem, self).get_absolute_url(link)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class BadRelatedItem(models.Model):
|
class BadRelatedItem(models.Model):
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.test.client import Client
|
from django.test.client import Client
|
||||||
|
from mock import patch, MagicMock
|
||||||
|
|
||||||
from openslides.agenda.models import Item, Speaker
|
from openslides.agenda.models import Item, Speaker
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
@ -69,24 +70,33 @@ class ListOfSpeakerModelTests(TestCase):
|
|||||||
self.assertIsNotNone(Speaker.objects.get(person=self.speaker1, item=self.item1).end_time)
|
self.assertIsNotNone(Speaker.objects.get(person=self.speaker1, item=self.item1).end_time)
|
||||||
self.assertIsNotNone(speaker2_item1.begin_time)
|
self.assertIsNotNone(speaker2_item1.begin_time)
|
||||||
|
|
||||||
def test_speach_coupled_with_countdown(self):
|
@patch('openslides.agenda.models.update_projector_overlay')
|
||||||
|
def test_speach_coupled_with_countdown(self, mock_update_projector_overlay):
|
||||||
config['agenda_couple_countdown_and_speakers'] = True
|
config['agenda_couple_countdown_and_speakers'] = True
|
||||||
self.assertTrue(config['countdown_state'] == 'inactive')
|
|
||||||
speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1)
|
speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1)
|
||||||
speaker1_item1.begin_speach()
|
self.item1.is_active_slide = MagicMock(return_value=True)
|
||||||
self.assertTrue(config['countdown_state'] == 'active')
|
|
||||||
speaker1_item1.end_speach()
|
|
||||||
self.assertTrue(config['countdown_state'] == 'paused')
|
|
||||||
|
|
||||||
def test_begin_speach_not_coupled_with_countdown(self):
|
|
||||||
config['agenda_couple_countdown_and_speakers'] = False
|
|
||||||
self.assertTrue(config['countdown_state'] == 'inactive')
|
|
||||||
speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1)
|
|
||||||
speaker1_item1.begin_speach()
|
speaker1_item1.begin_speach()
|
||||||
self.assertTrue(config['countdown_state'] == 'inactive')
|
self.assertEqual(config['countdown_state'], 'active')
|
||||||
|
mock_update_projector_overlay.assert_called_with('projector_countdown')
|
||||||
|
|
||||||
|
mock_update_projector_overlay.reset_mock()
|
||||||
|
speaker1_item1.end_speach()
|
||||||
|
self.assertEqual(config['countdown_state'], 'paused')
|
||||||
|
mock_update_projector_overlay.assert_called_with('projector_countdown')
|
||||||
|
|
||||||
|
@patch('openslides.agenda.models.update_projector_overlay')
|
||||||
|
def test_begin_speach_not_coupled_with_countdown(self, mock_update_projector_overlay):
|
||||||
|
config['agenda_couple_countdown_and_speakers'] = False
|
||||||
|
speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1)
|
||||||
|
|
||||||
|
speaker1_item1.begin_speach()
|
||||||
|
self.assertEqual(config['countdown_state'], 'inactive')
|
||||||
|
|
||||||
config['countdown_state'] = 'active'
|
config['countdown_state'] = 'active'
|
||||||
speaker1_item1.end_speach()
|
speaker1_item1.end_speach()
|
||||||
self.assertTrue(config['countdown_state'] == 'active')
|
self.assertEqual(config['countdown_state'], 'active')
|
||||||
|
self.assertFalse(mock_update_projector_overlay.called)
|
||||||
|
|
||||||
|
|
||||||
class SpeakerViewTestCase(TestCase):
|
class SpeakerViewTestCase(TestCase):
|
||||||
@ -233,7 +243,7 @@ class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
|
|||||||
self.assertRedirects(response, '/projector/dashboard/')
|
self.assertRedirects(response, '/projector/dashboard/')
|
||||||
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
|
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
|
||||||
|
|
||||||
set_active_slide('agenda', {'pk': 1})
|
set_active_slide('agenda', pk=1)
|
||||||
response = self.speaker1_client.get('/agenda/list_of_speakers/')
|
response = self.speaker1_client.get('/agenda/list_of_speakers/')
|
||||||
self.assertRedirects(response, '/agenda/1/')
|
self.assertRedirects(response, '/agenda/1/')
|
||||||
|
|
||||||
@ -242,7 +252,7 @@ class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
|
|||||||
self.assertRedirects(response, '/projector/dashboard/')
|
self.assertRedirects(response, '/projector/dashboard/')
|
||||||
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
|
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
|
||||||
|
|
||||||
set_active_slide('agenda', {'pk': 1})
|
set_active_slide('agenda', pk=1)
|
||||||
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
|
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
|
||||||
self.assertRedirects(response, '/agenda/1/')
|
self.assertRedirects(response, '/agenda/1/')
|
||||||
self.assertEqual(Speaker.objects.get(item__pk='1').person, self.speaker1)
|
self.assertEqual(Speaker.objects.get(item__pk='1').person, self.speaker1)
|
||||||
@ -258,7 +268,7 @@ class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
|
|||||||
self.assertRedirects(response, '/projector/dashboard/')
|
self.assertRedirects(response, '/projector/dashboard/')
|
||||||
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
|
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
|
||||||
|
|
||||||
set_active_slide('agenda', {'pk': 1})
|
set_active_slide('agenda', pk=1)
|
||||||
response = self.admin_client.get('/agenda/list_of_speakers/next/')
|
response = self.admin_client.get('/agenda/list_of_speakers/next/')
|
||||||
self.assertRedirects(response, '/projector/dashboard/')
|
self.assertRedirects(response, '/projector/dashboard/')
|
||||||
self.assertMessage(response, 'The list of speakers is empty.')
|
self.assertMessage(response, 'The list of speakers is empty.')
|
||||||
@ -274,7 +284,7 @@ class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
|
|||||||
self.assertRedirects(response, '/projector/dashboard/')
|
self.assertRedirects(response, '/projector/dashboard/')
|
||||||
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
|
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
|
||||||
|
|
||||||
set_active_slide('agenda', {'pk': 1})
|
set_active_slide('agenda', pk=1)
|
||||||
response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
|
response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
|
||||||
self.assertRedirects(response, '/projector/dashboard/')
|
self.assertRedirects(response, '/projector/dashboard/')
|
||||||
self.assertMessage(response, 'There is no one speaking at the moment.')
|
self.assertMessage(response, 'There is no one speaking at the moment.')
|
||||||
|
@ -8,6 +8,7 @@ from mock import patch
|
|||||||
from openslides.agenda.models import Item
|
from openslides.agenda.models import Item
|
||||||
from openslides.agenda.slides import agenda_slide
|
from openslides.agenda.slides import agenda_slide
|
||||||
from openslides.participant.models import User
|
from openslides.participant.models import User
|
||||||
|
from openslides.projector.api import set_active_slide
|
||||||
from openslides.utils.test import TestCase
|
from openslides.utils.test import TestCase
|
||||||
|
|
||||||
from .models import BadRelatedItem, RelatedItem
|
from .models import BadRelatedItem, RelatedItem
|
||||||
@ -77,7 +78,26 @@ class ItemTest(TestCase):
|
|||||||
def test_deleted_related_item(self):
|
def test_deleted_related_item(self):
|
||||||
self.related.delete()
|
self.related.delete()
|
||||||
self.assertFalse(RelatedItem.objects.all().exists())
|
self.assertFalse(RelatedItem.objects.all().exists())
|
||||||
self.assertEqual(Item.objects.get(pk=self.item5.pk).title, '< Item for deleted slide (ekdfjen458gj1siek45nv) >')
|
self.assertEqual(Item.objects.get(pk=self.item5.pk).title,
|
||||||
|
'< Item for deleted slide (ekdfjen458gj1siek45nv) >')
|
||||||
|
|
||||||
|
def test_related_item_get_absolute_url(self):
|
||||||
|
"""
|
||||||
|
Tests that the get_absolute_url method with the link 'projector'
|
||||||
|
and 'projector_preview' returns the absolute_url for the related
|
||||||
|
item.
|
||||||
|
"""
|
||||||
|
self.assertEqual(self.item5.get_absolute_url('projector'),
|
||||||
|
'/projector/activate/test_related_item/?pk=1')
|
||||||
|
self.assertEqual(self.item5.get_absolute_url('projector_preview'),
|
||||||
|
'/projector/preview/test_related_item/?pk=1')
|
||||||
|
|
||||||
|
def test_activate_related_item(self):
|
||||||
|
"""
|
||||||
|
The agenda item has to be active, if its related item is.
|
||||||
|
"""
|
||||||
|
set_active_slide('test_related_item', pk=1)
|
||||||
|
self.assertTrue(self.item5.is_active_slide)
|
||||||
|
|
||||||
def test_bad_related_item(self):
|
def test_bad_related_item(self):
|
||||||
bad = BadRelatedItem.objects.create(name='dhfne94irkgl2047fzvb')
|
bad = BadRelatedItem.objects.create(name='dhfne94irkgl2047fzvb')
|
||||||
|
@ -170,7 +170,7 @@ class ApiFunctions(TestCase):
|
|||||||
def test_set_active_slide(self, mock_update_projector, mock_update_projector_overlay):
|
def test_set_active_slide(self, mock_update_projector, mock_update_projector_overlay):
|
||||||
mock_config = {}
|
mock_config = {}
|
||||||
with patch('openslides.projector.api.config', mock_config):
|
with patch('openslides.projector.api.config', mock_config):
|
||||||
projector_api.set_active_slide('callback_name', {'some': 'kwargs'})
|
projector_api.set_active_slide('callback_name', some='kwargs')
|
||||||
self.assertEqual(mock_config,
|
self.assertEqual(mock_config,
|
||||||
{'projector_active_slide': {'callback': 'callback_name',
|
{'projector_active_slide': {'callback': 'callback_name',
|
||||||
'some': 'kwargs'}})
|
'some': 'kwargs'}})
|
||||||
|
21
tests/projector/test_signals.py
Normal file
21
tests/projector/test_signals.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from openslides.projector.signals import countdown
|
||||||
|
from openslides.utils.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class CountdownTest(TestCase):
|
||||||
|
def test_order_of_get_projector_js(self):
|
||||||
|
"""
|
||||||
|
Tests that the order of the js values is in the right order. Especially
|
||||||
|
the value 'call' has to come at the end.
|
||||||
|
"""
|
||||||
|
overlay = countdown('fake sender')
|
||||||
|
test_value = overlay.get_javascript()
|
||||||
|
|
||||||
|
self.assertIsInstance(test_value, dict)
|
||||||
|
self.assertEqual(
|
||||||
|
test_value.keys(),
|
||||||
|
['load_file', 'projector_countdown_start',
|
||||||
|
'projector_countdown_duration', 'projector_countdown_pause',
|
||||||
|
'projector_countdown_state', 'call'])
|
@ -3,6 +3,7 @@
|
|||||||
from django.test.client import Client, RequestFactory
|
from django.test.client import Client, RequestFactory
|
||||||
from mock import call, MagicMock, patch
|
from mock import call, MagicMock, patch
|
||||||
|
|
||||||
|
from openslides.config.api import config
|
||||||
from openslides.projector.models import ProjectorSlide
|
from openslides.projector.models import ProjectorSlide
|
||||||
from openslides.projector import views
|
from openslides.projector import views
|
||||||
from openslides.utils.test import TestCase
|
from openslides.utils.test import TestCase
|
||||||
@ -199,3 +200,19 @@ class CustomSlidesTest(TestCase):
|
|||||||
response = self.admin_client.post(url, {'yes': 'true'})
|
response = self.admin_client.post(url, {'yes': 'true'})
|
||||||
self.assertRedirects(response, '/projector/dashboard/')
|
self.assertRedirects(response, '/projector/dashboard/')
|
||||||
self.assertFalse(ProjectorSlide.objects.exists())
|
self.assertFalse(ProjectorSlide.objects.exists())
|
||||||
|
|
||||||
|
|
||||||
|
class CountdownControllView(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.admin_client = Client()
|
||||||
|
self.admin_client.login(username='admin', password='admin')
|
||||||
|
|
||||||
|
@patch('openslides.projector.views.reset_countdown')
|
||||||
|
def test_set_default(self, mock_reset_countdown):
|
||||||
|
"""
|
||||||
|
Test, that the url /countdown/set-default/ sets the time for the countdown
|
||||||
|
and reset the countdown.
|
||||||
|
"""
|
||||||
|
self.admin_client.get('/projector/countdown/set-default/', {'countdown_time': 42})
|
||||||
|
self.assertEqual(config['countdown_time'], 42)
|
||||||
|
mock_reset_countdown.assert_called_with()
|
||||||
|
Loading…
Reference in New Issue
Block a user