', rendered)
def test_underline_is_allowed(self):
self.assertIn('underlined ', str(markdown('underlined ')))
def test_markdown_title_strips_heading_marker(self):
self.assertEqual(markdown_title('## Ziel von my2dos\n\nText'), 'Ziel von my2dos')
class FormattingToolbarTests(TestCase):
def test_toolbar_and_shortcuts_are_available(self):
user = get_user_model().objects.create_user(username='formatting', password='secret')
self.client.force_login(user)
response = self.client.get(reverse('index'))
self.assertContains(response, 'class="format-toolbar')
self.assertContains(response, "formatSelection('bold')")
self.assertContains(response, "u:'underline'")
class NavigationAndLayoutTests(TestCase):
def setUp(self):
self.user = get_user_model().objects.create_user(username='navigation', password='secret')
self.preferences = UserPreference.objects.create(user=self.user)
self.client.force_login(self.user)
def test_desktop_navigation_contains_overview_hidden_links_and_kanban(self):
self.preferences.show_notes = False
self.preferences.save(update_fields=['show_notes'])
response = self.client.get(reverse('index'))
self.assertContains(response, 'd-none d-md-flex')
self.assertContains(response, f'{reverse("index")}?kind=note')
self.assertContains(response, reverse('kanban_list'))
def test_preference_selects_are_stacked(self):
response = self.client.get(reverse('settings'))
preferences = response.content.decode().split('id="preferences"', 1)[1].split('name="due_first"', 1)[0]
self.assertEqual(preferences.count(' '), 3)
def test_composer_menu_is_before_toolbar_and_file_controls(self):
response = self.client.get(reverse('index'))
html = response.content.decode()
menu_position = html.index('composer-slash-menu')
toolbar_position = html.index('format-toolbar', menu_position)
file_position = html.index('desktop-file', toolbar_position)
self.assertLess(menu_position, toolbar_position)
self.assertLess(toolbar_position, file_position)
class AttachmentEditTests(TestCase):
def setUp(self):
self.media_root = tempfile.mkdtemp()
self.override = override_settings(MEDIA_ROOT=self.media_root)
self.override.enable()
self.user = get_user_model().objects.create_user(username='attachment-editor', password='secret')
self.client.force_login(self.user)
def tearDown(self):
self.override.disable()
shutil.rmtree(self.media_root, ignore_errors=True)
def test_uploaded_file_is_visible_after_first_inline_edit_save(self):
item = Item.objects.create(owner=self.user, content='Original')
uploaded = SimpleUploadedFile('hello.txt', b'hello', content_type='text/plain')
response = self.client.post(reverse('edit_item', args=[item.id]), {
'content': 'Original', 'comment': '', 'visibility': Item.Visibility.PRIVATE, 'url': '', 'files': uploaded,
}, HTTP_HX_REQUEST='true')
self.assertEqual(response.status_code, 200)
self.assertEqual(item.attachments.count(), 1)
self.assertContains(response, 'hello.txt')
def test_existing_attachments_are_shown_and_can_be_deleted_in_editor(self):
item = Item.objects.create(owner=self.user, content='Original')
attachment = Attachment.objects.create(item=item, file=SimpleUploadedFile('duplicate.txt', b'duplicate', content_type='text/plain'))
response = self.client.get(reverse('item_editor', args=[item.id]))
self.assertContains(response, 'duplicate.txt')
self.assertContains(response, 'name="delete_attachments"')
response = self.client.post(reverse('item_editor', args=[item.id]), {
'content': 'Original', 'comment': '', 'visibility': Item.Visibility.PRIVATE, 'url': '', 'delete_attachments': [str(attachment.id)],
})
self.assertRedirects(response, item.get_absolute_url())
self.assertFalse(item.attachments.exists())
def test_detail_editor_uses_original_return_url_not_partial_endpoint(self):
item = Item.objects.create(owner=self.user, content='Original')
response = self.client.get(f'{item.get_absolute_url()}?next=/')
self.assertContains(response, f'{reverse("item_editor", args=[item.id])}?next=/')
self.assertNotContains(response, reverse('item_card', args=[item.id]))
class TeamItemEditTests(TestCase):
def setUp(self):
self.user = get_user_model().objects.create_user(username='team-editor', password='secret')
self.team = Team.objects.create(name='Docs team', slug='docs', owner=self.user)
TeamMembership.objects.create(team=self.team, user=self.user, role=TeamMembership.Role.OWNER)
self.client.force_login(self.user)
def test_editing_team_item_preserves_team_link(self):
item = Item.objects.create(
owner=self.user, team=self.team, kind=Item.Kind.TODO,
content='Original #docs', visibility=Item.Visibility.TEAM,
)
item.sync_metadata()
response = self.client.post(reverse('edit_item', args=[item.id]), {
'content': 'Updated #docs', 'comment': '', 'visibility': Item.Visibility.TEAM, 'url': '',
})
self.assertEqual(response.status_code, 200)
item.refresh_from_db()
self.assertEqual(item.team, self.team)
self.assertEqual(item.visibility, Item.Visibility.TEAM)
def test_team_visibility_is_selected_in_edit_forms(self):
item = Item.objects.create(
owner=self.user, team=self.team, content='Original #docs', visibility=Item.Visibility.TEAM,
)
response = self.client.get(reverse('edit_item', args=[item.id]))
self.assertContains(response, '', html=False)
response = self.client.get(reverse('item_editor', args=[item.id]))
self.assertContains(response, ' ', html=False)
def test_team_item_can_be_made_public_even_with_team_tag(self):
item = Item.objects.create(
owner=self.user, team=self.team, content='Original #docs', visibility=Item.Visibility.TEAM,
)
item.sync_metadata()
response = self.client.post(reverse('item_editor', args=[item.id]), {
'content': 'Updated #docs', 'comment': '', 'visibility': Item.Visibility.PUBLIC, 'url': '',
})
self.assertRedirects(response, item.get_absolute_url())
item.refresh_from_db()
self.assertEqual(item.visibility, Item.Visibility.PUBLIC)
self.assertIsNone(item.team)
class CollectionTests(TestCase):
def setUp(self):
self.user = get_user_model().objects.create_user(username='collector', password='secret')
self.client.force_login(self.user)
def test_create_collection_and_add_compatible_note(self):
response = self.client.post(reverse('collection_create'), {
'title': 'Documentation', 'description': 'Guide', 'visibility': Item.Visibility.PRIVATE, 'mode': Collection.Mode.MANUAL,
})
collection = Collection.objects.get()
self.assertRedirects(response, collection.get_absolute_url())
note = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='Introduction')
manage_url = reverse('collection_manage', args=[collection.id])
response = self.client.post(manage_url, {'action': 'add', 'item_id': note.id})
self.assertRedirects(response, manage_url)
self.assertTrue(CollectionSection.objects.filter(collection=collection, item=note).exists())
def test_public_note_can_be_added_to_private_collection_without_conversion(self):
collection = Collection.objects.create(owner=self.user, title='Private guide', visibility=Item.Visibility.PRIVATE)
note = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='Already public', visibility=Item.Visibility.PUBLIC)
manage_url = reverse('collection_manage', args=[collection.id])
response = self.client.post(manage_url, {'action': 'add', 'item_id': note.id})
self.assertRedirects(response, manage_url)
self.assertTrue(collection.sections.filter(item=note).exists())
note.refresh_from_db()
self.assertEqual(note.visibility, Item.Visibility.PUBLIC)
def test_public_collection_asks_before_copying_private_note(self):
collection = Collection.objects.create(owner=self.user, title='Public guide', visibility=Item.Visibility.PUBLIC)
note = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='Private section')
manage_url = reverse('collection_manage', args=[collection.id])
response = self.client.post(manage_url, {'action': 'add', 'item_id': note.id})
self.assertContains(response, 'name="mode" value="copy"')
response = self.client.post(manage_url, {
'action': 'confirm_add', 'item_id': note.id, 'mode': 'copy',
})
self.assertRedirects(response, manage_url)
copied = collection.sections.get().item
self.assertNotEqual(copied.id, note.id)
self.assertEqual(copied.visibility, Item.Visibility.PUBLIC)
note.refresh_from_db()
self.assertEqual(note.visibility, Item.Visibility.PRIVATE)
def test_note_used_by_collection_cannot_be_deleted(self):
collection = Collection.objects.create(owner=self.user, title='Protected')
note = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='Do not delete')
CollectionSection.objects.create(collection=collection, item=note, position=0)
response = self.client.post(reverse('delete_item', args=[note.id]))
self.assertEqual(response.status_code, 409)
self.assertTrue(Item.objects.filter(pk=note.id).exists())
def test_team_selection_creates_team_collection(self):
team = Team.objects.create(name='Docs team', slug='docs', owner=self.user)
TeamMembership.objects.create(team=team, user=self.user, role=TeamMembership.Role.OWNER)
response = self.client.post(reverse('collection_create'), {
'title': 'Team guide', 'visibility': Item.Visibility.PRIVATE,
'mode': Collection.Mode.MANUAL, 'team': team.id,
})
collection = Collection.objects.get()
self.assertRedirects(response, collection.get_absolute_url())
self.assertEqual(collection.team, team)
self.assertEqual(collection.visibility, Item.Visibility.TEAM)
def test_other_users_public_notes_are_not_offered_for_embedding(self):
other = get_user_model().objects.create_user(username='other')
public_note = Item.objects.create(owner=other, kind=Item.Kind.NOTE, content='External', visibility=Item.Visibility.PUBLIC)
collection = Collection.objects.create(owner=self.user, title='Own collection')
response = self.client.get(reverse('collection_manage', args=[collection.id]))
self.assertNotContains(response, f'name="item_id" value="{public_note.id}"')
def test_collection_form_separates_teams_and_content_tags(self):
team = Team.objects.create(name='Docs team', slug='docs', owner=self.user)
TeamMembership.objects.create(team=team, user=self.user, role=TeamMembership.Role.OWNER)
Tag.objects.create(name='docs')
note = Item.objects.create(owner=self.user, content='#ordinary')
note.sync_metadata()
response = self.client.get(reverse('collection_create'))
form = response.context['form']
self.assertEqual(list(form.fields['team'].queryset), [team])
self.assertEqual(list(form.fields['filter_tags'].queryset.values_list('name', flat=True)), ['ordinary'])
def test_tutorial_is_listed_but_chapter_is_hidden_from_collection_overview(self):
tutorial = Collection.objects.create(owner=self.user, title='my2dos Tutorial', mode=Collection.Mode.TUTORIAL)
chapter = Collection.objects.create(owner=self.user, title='my2dos Typen', mode=Collection.Mode.CHAPTER)
response = self.client.get(reverse('collection_list'))
self.assertContains(response, tutorial.title)
self.assertNotIn(chapter, [entry['collection'] for entry in response.context['entries']])
def test_collection_list_can_filter_collection_types(self):
Collection.objects.create(owner=self.user, title='Manual', mode=Collection.Mode.MANUAL)
Collection.objects.create(owner=self.user, title='Tutorial', mode=Collection.Mode.TUTORIAL)
Collection.objects.create(owner=self.user, title='Tagged', mode=Collection.Mode.TAGS)
Collection.objects.create(owner=self.user, title='Chapter', mode=Collection.Mode.CHAPTER)
response = self.client.get(f'{reverse("collection_list")}?mode=chapter')
self.assertEqual([entry['collection'].title for entry in response.context['entries']], ['Chapter'])
def test_orphan_chapters_are_shown_as_warning(self):
Collection.objects.create(owner=self.user, title='Lonely chapter', mode=Collection.Mode.CHAPTER)
response = self.client.get(reverse('collection_list'))
self.assertContains(response, 'Verwaiste Kapitel')
self.assertContains(response, 'Lonely chapter')
def test_tutorial_can_include_chapter_collection(self):
tutorial = Collection.objects.create(owner=self.user, title='my2dos Tutorial', mode=Collection.Mode.TUTORIAL)
chapter = Collection.objects.create(owner=self.user, title='my2dos Typen', mode=Collection.Mode.CHAPTER)
note = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='## Todos')
CollectionSection.objects.create(collection=chapter, item=note, position=0)
manage_url = reverse('collection_manage', args=[tutorial.id])
response = self.client.post(manage_url, {'action': 'add_collection', 'collection_id': chapter.id})
self.assertRedirects(response, manage_url)
self.assertTrue(tutorial.sections.filter(sub_collection=chapter).exists())
response = self.client.get(tutorial.get_absolute_url())
self.assertContains(response, 'my2dos Typen ', html=True)
self.assertContains(response, 'Todos ', html=True)
def test_collection_overview_is_available_on_mobile(self):
response = self.client.get(reverse('collection_list'))
self.assertNotContains(response, 'Collections are currently available on desktop only.')
self.assertContains(response, 'Sammlungen')
def test_collection_manage_links_to_note_editor_with_return_target(self):
collection = Collection.objects.create(owner=self.user, title='Reading view')
manage_url = reverse('collection_manage', args=[collection.id])
response = self.client.get(manage_url)
self.assertContains(response, f'{reverse("new_item")}?kind=note&collection={collection.id}&next={manage_url}')
def test_create_note_for_collection_in_editor_adds_it_and_returns(self):
collection = Collection.objects.create(owner=self.user, title='Reading view')
manage_url = reverse('collection_manage', args=[collection.id])
response = self.client.post(f'{reverse("new_item")}?kind=note&collection={collection.id}&next={manage_url}', {
'collection': collection.id, 'next': manage_url, 'content': '## Fresh note #new',
})
note = Item.objects.get(content='## Fresh note #new')
self.assertRedirects(response, manage_url)
self.assertTrue(collection.sections.filter(item=note).exists())
self.assertEqual(note.visibility, collection.visibility)
self.assertEqual(list(note.tags.values_list('name', flat=True)), ['new'])
def test_create_chapter_from_tutorial_manage_adds_and_opens_it(self):
tutorial = Collection.objects.create(owner=self.user, title='my2dos Tutorial', mode=Collection.Mode.TUTORIAL)
response = self.client.post(reverse('collection_manage', args=[tutorial.id]), {'action': 'create_chapter', 'title': 'Tags'})
chapter = Collection.objects.get(title='Tags')
self.assertEqual(chapter.mode, Collection.Mode.CHAPTER)
self.assertTrue(tutorial.sections.filter(sub_collection=chapter).exists())
self.assertRedirects(response, reverse('collection_manage', args=[chapter.id]))
def test_collection_sections_can_be_reordered_by_position(self):
collection = Collection.objects.create(owner=self.user, title='Reading view')
first = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='First')
second = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='Second')
first_section = CollectionSection.objects.create(collection=collection, item=first, position=0)
second_section = CollectionSection.objects.create(collection=collection, item=second, position=1)
response = self.client.post(reverse('collection_manage', args=[collection.id]), {
'action': 'move_to', 'section_id': second_section.id, 'position': 0,
})
self.assertRedirects(response, reverse('collection_manage', args=[collection.id]))
self.assertEqual(list(collection.sections.order_by('position').values_list('id', flat=True)), [second_section.id, first_section.id])
def test_collection_detail_is_available_on_mobile(self):
collection = Collection.objects.create(owner=self.user, title='Mobile reading')
response = self.client.get(collection.get_absolute_url())
self.assertNotContains(response, 'Collections are currently available on desktop only.')
self.assertContains(response, 'collection-document')
def test_tag_collection_is_dynamic_and_sorted_by_creation(self):
first = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='First #guide #basics')
first.sync_metadata()
Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='Ignored #guide').sync_metadata()
second = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='Second #guide #basics')
second.sync_metadata()
collection = Collection.objects.create(owner=self.user, title='Automatic', mode=Collection.Mode.TAGS)
collection.filter_tags.set(Tag.objects.filter(name__in=['guide', 'basics']))
response = self.client.get(collection.get_absolute_url())
html = response.content.decode()
self.assertContains(response, 'Tag-Sammlung')
self.assertLess(html.index('First'), html.index('Second'))
self.assertNotContains(response, 'First #guide')
self.assertNotContains(response, 'Second #guide')
self.assertNotContains(response, 'Ignored')
self.assertContains(response, reverse('collection_edit', args=[collection.id]))
self.assertNotContains(response, reverse('collection_manage', args=[collection.id]))
manage_response = self.client.get(reverse('collection_manage', args=[collection.id]))
self.assertRedirects(manage_response, reverse('collection_edit', args=[collection.id]))
def test_collection_opens_as_continuous_reading_view(self):
collection = Collection.objects.create(owner=self.user, title='Reading view')
first = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='First paragraph')
second = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='Second paragraph')
CollectionSection.objects.create(collection=collection, item=first, title='First', position=0)
CollectionSection.objects.create(collection=collection, item=second, position=1)
response = self.client.get(collection.get_absolute_url())
self.assertContains(response, 'collection-flow')
self.assertContains(response, reverse('collection_manage', args=[collection.id]))
self.assertNotContains(response, 'Add existing note')
self.assertNotContains(response, 'section_id')
def test_collection_reading_view_hides_note_tags(self):
collection = Collection.objects.create(owner=self.user, title='Reading view')
note = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='## Section #tutorial\nText #my2dos and `#code`')
CollectionSection.objects.create(collection=collection, item=note, position=0)
response = self.client.get(collection.get_absolute_url())
self.assertContains(response, 'Section ', html=True)
self.assertContains(response, '#code', html=True)
self.assertNotContains(response, '#tutorial')
self.assertNotContains(response, '#my2dos')
def test_collection_manage_links_note_editor_back_to_collection(self):
collection = Collection.objects.create(owner=self.user, title='Reading view')
note = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='## Section')
CollectionSection.objects.create(collection=collection, item=note, position=0)
manage_url = reverse('collection_manage', args=[collection.id])
response = self.client.get(manage_url)
self.assertContains(response, 'id="collection-manage"')
self.assertContains(response, 'hx-boost="true"')
self.assertContains(response, 'hx-swap="outerHTML show:none"')
self.assertContains(response, f'{reverse("item_editor", args=[note.id])}?next={manage_url}')
self.assertContains(response, 'href="#section-1">Section')
def test_collection_manage_preserves_search_when_adding_note(self):
collection = Collection.objects.create(owner=self.user, title='Reading view')
note = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='Findable note')
manage_url = reverse('collection_manage', args=[collection.id])
response = self.client.post(f'{manage_url}?q=Findable', {'action': 'add', 'item_id': note.id})
self.assertRedirects(response, f'{manage_url}?q=Findable')
def test_collection_manage_marks_already_added_notes(self):
collection = Collection.objects.create(owner=self.user, title='Reading view')
note = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='Findable note')
CollectionSection.objects.create(collection=collection, item=note, position=0)
response = self.client.get(f'{reverse("collection_manage", args=[collection.id])}?q=Findable')
self.assertContains(response, 'Bereits in dieser Sammlung')
self.assertContains(response, '1× verwendet')
self.assertContains(response, 'disabled')
def test_collection_can_include_collection(self):
parent = Collection.objects.create(owner=self.user, title='Parent')
child = Collection.objects.create(owner=self.user, title='Child')
note = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='## Child note #internal')
CollectionSection.objects.create(collection=child, item=note, position=0)
manage_url = reverse('collection_manage', args=[parent.id])
response = self.client.post(manage_url, {'action': 'add_collection', 'collection_id': child.id})
self.assertRedirects(response, manage_url)
self.assertTrue(parent.sections.filter(sub_collection=child).exists())
response = self.client.get(parent.get_absolute_url())
self.assertContains(response, 'Child ', html=True)
self.assertContains(response, 'Child note ', html=True)
self.assertNotContains(response, '#internal')
def test_collection_loop_is_rejected(self):
parent = Collection.objects.create(owner=self.user, title='Parent')
child = Collection.objects.create(owner=self.user, title='Child')
CollectionSection.objects.create(collection=parent, sub_collection=child, position=0)
manage_url = reverse('collection_manage', args=[child.id])
response = self.client.post(manage_url, {'action': 'add_collection', 'collection_id': parent.id}, follow=True)
self.assertContains(response, 'collection loop')
self.assertFalse(child.sections.filter(sub_collection=parent).exists())
def test_public_collection_cannot_include_private_collection(self):
parent = Collection.objects.create(owner=self.user, title='Parent', visibility=Item.Visibility.PUBLIC)
child = Collection.objects.create(owner=self.user, title='Child', visibility=Item.Visibility.PRIVATE)
manage_url = reverse('collection_manage', args=[parent.id])
response = self.client.post(manage_url, {'action': 'add_collection', 'collection_id': child.id}, follow=True)
self.assertContains(response, 'incompatible visibility')
self.assertFalse(parent.sections.filter(sub_collection=child).exists())
def test_private_collection_can_include_public_collection(self):
parent = Collection.objects.create(owner=self.user, title='Parent', visibility=Item.Visibility.PRIVATE)
child = Collection.objects.create(owner=self.user, title='Child', visibility=Item.Visibility.PUBLIC)
manage_url = reverse('collection_manage', args=[parent.id])
response = self.client.post(manage_url, {'action': 'add_collection', 'collection_id': child.id})
self.assertRedirects(response, manage_url)
self.assertTrue(parent.sections.filter(sub_collection=child).exists())
def test_public_collection_is_visible_without_login(self):
collection = Collection.objects.create(owner=self.user, title='Public', visibility=Item.Visibility.PUBLIC)
self.client.logout()
response = self.client.get(collection.get_absolute_url())
self.assertEqual(response.status_code, 200)
self.assertNotContains(response, reverse('collection_manage', args=[collection.id]))
def test_collection_tag_is_not_removed_as_unused(self):
tag = Tag.objects.create(name='documentation')
collection = Collection.objects.create(owner=self.user, title='Tagged', mode=Collection.Mode.TAGS)
collection.filter_tags.add(tag)
note = Item.objects.create(owner=self.user, content='#temporary')
note.sync_metadata()
note.content = ''
note.save()
note.sync_metadata()
self.assertTrue(Tag.objects.filter(pk=tag.pk).exists())
def test_api_cannot_make_collection_note_incompatible(self):
collection = Collection.objects.create(owner=self.user, title='Public', visibility=Item.Visibility.PUBLIC)
note = Item.objects.create(owner=self.user, kind=Item.Kind.NOTE, content='Published', visibility=Item.Visibility.PUBLIC)
CollectionSection.objects.create(collection=collection, item=note, position=0)
response = self.client.patch(
reverse('api_item_detail', args=[note.id]),
data='{"visibility":"private"}', content_type='application/json',
)
self.assertEqual(response.status_code, 409)
note.refresh_from_db()
self.assertEqual(note.visibility, Item.Visibility.PUBLIC)
class ApiItemFilterTests(TestCase):
def setUp(self):
self.user = get_user_model().objects.create_user(username='api-filter', password='secret')
self.help_tag = Tag.objects.create(name='help')
self.api_key = ApiKey.objects.create(owner=self.user, name='Help CLI')
self.api_key.tags.add(self.help_tag)
def create_item(self, content):
item = Item.objects.create(owner=self.user, content=content)
item.sync_metadata()
return item
def test_api_key_scope_and_request_tags_are_combined(self):
matching = self.create_item('Keyboard shortcuts #help #tasten')
self.create_item('Git help #help #git')
self.create_item('Private keyboard note #tasten')
response = self.client.get(
reverse('api_items'), {'tag': 'tasten', 'tag_mode': 'all'},
HTTP_X_API_KEY=self.api_key.token,
)
self.assertEqual(response.status_code, 200)
ids = [item['id'] for item in response.json()['items']]
self.assertEqual(ids, [matching.id])
def test_api_items_supports_limit_and_search(self):
self.create_item('First #help #shell')
second = self.create_item('Second #help #shell keyboard')
response = self.client.get(
reverse('api_items'), {'tag': 'shell', 'q': 'keyboard', 'limit': '1'},
HTTP_X_API_KEY=self.api_key.token,
)
self.assertEqual(response.status_code, 200)
items = response.json()['items']
self.assertEqual(len(items), 1)
self.assertEqual(items[0]['id'], second.id)
class CopyButtonTests(TestCase):
def test_item_has_copy_button_with_original_text(self):
user = get_user_model().objects.create_user(username='tester', password='secret')
item = Item.objects.create(owner=user, content='Text to copy')
self.client.force_login(user)
response = self.client.get(reverse('item_detail', args=[item.pk]))
self.assertContains(response, 'class="copy-item')
self.assertContains(response, 'Text to copy')