471 lines
24 KiB
Python
471 lines
24 KiB
Python
from django.contrib.auth import get_user_model
|
|
from django.test import TestCase
|
|
from django.urls import reverse
|
|
|
|
from .models import ApiKey, Collection, CollectionSection, Item, Tag, Team, TeamMembership, UserPreference
|
|
from .templatetags.markdown_extras import markdown, markdown_title
|
|
from .views import parse_due_command, parse_quick_content
|
|
|
|
|
|
class ItemMetadataTests(TestCase):
|
|
def setUp(self):
|
|
self.user = get_user_model().objects.create_user(username='tester', password='secret')
|
|
|
|
def test_code_sections_are_ignored_when_extracting_tags(self):
|
|
item = Item.objects.create(
|
|
owner=self.user,
|
|
content='Outside #real\n```python\n# fenced\n```\nInline `#inline` text',
|
|
)
|
|
item.sync_metadata()
|
|
self.assertEqual(list(item.tags.values_list('name', flat=True)), ['real'])
|
|
self.assertFalse(Tag.objects.filter(name__in=['fenced', 'inline']).exists())
|
|
|
|
def test_unused_tags_are_removed_after_sync_and_delete(self):
|
|
item = Item.objects.create(owner=self.user, content='#old')
|
|
item.sync_metadata()
|
|
item.content = '#new'
|
|
item.save()
|
|
item.sync_metadata()
|
|
self.assertFalse(Tag.objects.filter(name='old').exists())
|
|
self.assertTrue(Tag.objects.filter(name='new').exists())
|
|
|
|
item.delete()
|
|
self.assertFalse(Tag.objects.filter(name='new').exists())
|
|
|
|
|
|
class DefaultItemKindTests(TestCase):
|
|
def setUp(self):
|
|
self.user = get_user_model().objects.create_user(username='tester', password='secret')
|
|
self.client.force_login(self.user)
|
|
self.preferences = UserPreference.objects.create(user=self.user, default_item_kind=Item.Kind.TODO)
|
|
|
|
def test_quick_create_uses_user_default(self):
|
|
response = self.client.post(reverse('index'), {'content': 'Default task'})
|
|
self.assertRedirects(response, reverse('index'))
|
|
self.assertEqual(Item.objects.get().kind, Item.Kind.TODO)
|
|
|
|
def test_explicit_command_overrides_default(self):
|
|
kind, _, content, _ = parse_quick_content('/note Explicit note', Item.Kind.TODO)
|
|
self.assertEqual(kind, Item.Kind.NOTE)
|
|
self.assertEqual(content, 'Explicit note')
|
|
|
|
def test_new_item_editor_uses_user_default(self):
|
|
response = self.client.get(reverse('new_item'))
|
|
self.assertEqual(response.context['requested_kind'], Item.Kind.TODO)
|
|
|
|
|
|
class DueDateCommandTests(TestCase):
|
|
def setUp(self):
|
|
self.user = get_user_model().objects.create_user(username='dates', password='secret')
|
|
self.client.force_login(self.user)
|
|
|
|
def test_datum_command_is_removed_and_parsed(self):
|
|
cleaned, due_at, found = parse_due_command('/todo Call back /datum 20.10.26 14:30 #work')
|
|
self.assertTrue(found)
|
|
self.assertEqual(cleaned, '/todo Call back #work')
|
|
self.assertEqual((due_at.year, due_at.month, due_at.day, due_at.hour, due_at.minute), (2026, 10, 20, 14, 30))
|
|
|
|
def test_relative_command_sets_due_date_on_quick_create(self):
|
|
response = self.client.post(reverse('index'), {'content': '/todo Call back /morgen'})
|
|
self.assertRedirects(response, reverse('index'))
|
|
item = Item.objects.get()
|
|
self.assertNotIn('/morgen', item.content)
|
|
self.assertIsNotNone(item.due_at)
|
|
|
|
def test_type_and_date_commands_are_available_in_menu(self):
|
|
response = self.client.get(reverse('index'))
|
|
self.assertContains(response, "{token:'/note'")
|
|
self.assertContains(response, "{token:'/datum'")
|
|
self.assertContains(response, 'queryStart')
|
|
self.assertContains(response, "group:'Typ'")
|
|
|
|
|
|
class SlashMenuContextTests(TestCase):
|
|
def test_type_command_only_counts_at_start(self):
|
|
leading_kind, _, leading_content, _ = parse_quick_content('/todo Leading')
|
|
later_kind, _, later_content, _ = parse_quick_content('Text /todo later')
|
|
self.assertEqual(leading_kind, Item.Kind.TODO)
|
|
self.assertEqual(leading_content, 'Leading')
|
|
self.assertEqual(later_kind, Item.Kind.NOTE)
|
|
self.assertEqual(later_content, 'Text /todo later')
|
|
|
|
|
|
class MarkdownHeadingTests(TestCase):
|
|
def test_tag_at_line_start_is_not_a_heading(self):
|
|
rendered = str(markdown('#project starts here'))
|
|
self.assertIn('<p>#project starts here</p>', rendered)
|
|
self.assertNotIn('<h1>', rendered)
|
|
|
|
def test_heading_with_space_remains_a_heading(self):
|
|
self.assertIn('<h1>Project</h1>', str(markdown('# Project')))
|
|
self.assertIn('<h2>Section</h2>', str(markdown('## Section')))
|
|
|
|
def test_hash_in_fenced_code_is_unchanged(self):
|
|
rendered = str(markdown('```python\n# comment\n```'))
|
|
self.assertIn('# comment', rendered)
|
|
self.assertNotIn('\\# comment', rendered)
|
|
self.assertNotIn('<h1>', rendered)
|
|
|
|
def test_hash_in_inline_code_is_not_a_heading(self):
|
|
rendered = str(markdown('```# Das ist Kommentar im Code```'))
|
|
self.assertIn('<code># Das ist Kommentar im Code</code>', rendered)
|
|
self.assertNotIn('<h1>', rendered)
|
|
|
|
def test_underline_is_allowed(self):
|
|
self.assertIn('<u>underlined</u>', str(markdown('<u>underlined</u>')))
|
|
|
|
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('<div class="col-12"><label class="form-label">'), 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 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, '<option value="team" selected>', html=False)
|
|
response = self.client.get(reverse('item_editor', args=[item.id]))
|
|
self.assertContains(response, '<option value="team" selected>', 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_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, '<h2>Section</h2>', html=True)
|
|
self.assertContains(response, '<code>#code</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, f'{reverse("item_editor", args=[note.id])}?next={manage_url}')
|
|
self.assertContains(response, '>Section</')
|
|
self.assertNotContains(response, '>## Section</')
|
|
|
|
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, '<h2>Child</h2>', html=True)
|
|
self.assertContains(response, '<h3>Child note</h3>', 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')
|