from django.contrib.auth import get_user_model from django.test import TestCase from django.urls import reverse from .models import Item, Tag, UserPreference 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, "let hasType=") 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')