diff --git a/README.md b/README.md index 2a713aa0..87e376e6 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,12 @@ Join the chat on [Discord](https://discord.gg/r5nXDsQ) Inspired by [arkt.is/t/](http://arkt.is/t/Yy53aWR0aD0yZTM7eC5maWxsUmVjdCgxNTAsMTUwKlModCkrMTUwLDE1MCwxNTAp) -## Pre-requisites and first-time installation +## Pre-requisites and first-time installation (Linux) * Install `npm` * `sudo apt install npm` -* Get packages needed for server and clone the repository +* Install Python and necessary Python development packages +* `sudo apt install python python-dev` +* Get packages needed for server and clone the repository * `sudo apt install git virtualenv python-pip` * `git clone https://github.com/lionleaf/dwitter.git` diff --git a/dwitter/models.py b/dwitter/models.py index 27eebaeb..e770b97c 100644 --- a/dwitter/models.py +++ b/dwitter/models.py @@ -55,16 +55,12 @@ def save(self, *args, **kwargs): @cached_property def top_comment(self): - """ - Return the top comment. This is mainly a caching optimization to avoid queries - """ + # return the top comment - this is mainly a caching optimization to avoid queries return self.comments.first() @cached_property def has_sticky_comment(self): - """ - True when first comment should be stickied (first comment author == dweet author) - """ + # return true when first comment should be stickied (first comment author == dweet author) if self.comments.first() is None: return False return self.comments.first().author == self.author @@ -73,11 +69,8 @@ def __str__(self): return 'd/' + str(self.id) + ' (' + self.author.username + ')' def calculate_hotness(self, is_new): - """ - Hotness is inspired by the Hackernews ranking algorithm - Read more here: - https://medium.com/hacking-and-gonzo/how-hacker-news-ranking-algorithm-works-1d9b0cf2c08d - """ + # hotness is inspired by the Hackernews ranking algorithm, read more here: + # https://medium.com/hacking-and-gonzo/how-hacker-news-ranking-algorithm-works-1d9b0cf2c08d def epoch_seconds(date): epoch = datetime(2015, 5, 5) # arbitrary start date before Dwitter existed naive = date.replace(tzinfo=None) @@ -100,7 +93,7 @@ def epoch_seconds(date): @receiver(m2m_changed, sender=Dweet.likes.through, dispatch_uid="recalculate_hotness") def recalc_hotness(sender, instance, action, **kwargs): if action in ("post_add", "post_remove", "post_clear"): - instance.save() # Trigger save on m2m_change forces calculate_hotness again + instance.save() # trigger save on m2m_change - forces calculate_hotness again class Comment(models.Model): @@ -130,13 +123,11 @@ def __str__(self): return '#' + self.name -# Go through hashtags mentioned in the comment -# and add them to the parent dweet. -# Should be idempotent. +# go through hashtags mentioned in the comment and add them to the parent dweet +# should be idempotent @receiver(post_save, sender=Comment, dispatch_uid="add_hashtags_from_comment") def add_hashtags(sender, instance, **kwargs): - hash_pattern = re.compile(r'#(?P[_a-zA-Z][_a-zA-Z\d]*)') - for hashtag in re.findall(hash_pattern, instance.text): + for hashtag in re.findall(r'(?
  • {{ dweet.top_comment.author.username }}: - {{ dweet.top_comment.text | force_escape | insert_code_blocks | urlizetrunc:45 | insert_magic_links }} + {{ dweet.top_comment.text | force_escape | insert_magic_links | urlizetrunc:45 | insert_code_blocks }}
  • {% endif %} @@ -144,7 +144,7 @@ {% if comment != dweet.top_comment or not dweet.has_sticky_comment %}
  • {{ comment.author.username }}: - {{ comment.text | force_escape | insert_code_blocks | urlizetrunc:45 | insert_magic_links }} + {{ comment.text | force_escape | insert_magic_links | urlizetrunc:45 | insert_code_blocks }}
  • {% endif %} {% endfor %} diff --git a/dwitter/templatetags/insert_code_blocks.py b/dwitter/templatetags/insert_code_blocks.py index b2cf818b..01642ced 100644 --- a/dwitter/templatetags/insert_code_blocks.py +++ b/dwitter/templatetags/insert_code_blocks.py @@ -9,6 +9,11 @@ def to_code_block(m): code = m.group('code') code = re.sub(r'\\`', '`', code) + # remove anchors from code blocks + a = re.findall(r'((?P[^<]*))', code) + for i in range(len(a)): + code = code.replace(a[i][0], a[i][1]) + return '%s' % code diff --git a/dwitter/templatetags/insert_magic_links.py b/dwitter/templatetags/insert_magic_links.py index 08b340b8..71eb8db0 100644 --- a/dwitter/templatetags/insert_magic_links.py +++ b/dwitter/templatetags/insert_magic_links.py @@ -4,6 +4,20 @@ register = template.Library() +# find all long d/ and u/ links in http:// form through regex, and +# use replace() to singularly shorten each match into the d/ or u/ form +def autocrop_urls(m): + d_links = re.findall(r'(?{1}'.format(url, tag) return text.replace(tag, result) @register.filter(is_safe=True) def insert_magic_links(text): + text = autocrop_urls(text) text = re.sub( - r'(?:^|(?<=\s))' # start of string or whitespace - r'/?' # optional / - r'(?P' # capture original pattern - r'[^a-zA-Z\d]?d/(?P\d+)[^a-zA-Z]?' # dweet reference - r'|' # or - r'[^a-zA-Z\d]?u/(?P[\w.@+-]+)[^a-zA-Z\d]?)' # user reference - r'(?=$|\s|#)', # end of string, whitespace or hashtag + # start of string or whitespace + r'(?:^|(?<=\s))' + # optional / + r'/?' + # capture original pattern + r'(?P' + # dweet reference + r'[^a-zA-Z\d]?d/(?P\d+)(\s|\:|\;|\,|\!|\?|\.|\Z)?' + # or + r'|' + # user reference + r'(?[\w.@+-]+)(\s|\:|\;|\,|\!|\?|\.|\Z))' + # end of string, whitespace or hashtag: + r'(?=$|\s|#)', user_dweet_to_link, text ) text = re.sub( - r'(?P' # capture original pattern - r'#(?P[_a-zA-Z][_a-zA-Z\d]*)[^_a-zA-Z\d]?)', # hashtag + # capture original pattern + r'(?P' + # hashtag - check for whitespace precedence and word boundaries + r'(?[_a-zA-Z\d]+)(\s|\:|\;|\,|\!|\?|\.|\Z))', hashtag_to_link, text ) diff --git a/dwitter/tests/dweet/test_dweet_comments.py b/dwitter/tests/dweet/test_dweet_comments.py index 6cb27948..f7fa8dd5 100644 --- a/dwitter/tests/dweet/test_dweet_comments.py +++ b/dwitter/tests/dweet/test_dweet_comments.py @@ -14,6 +14,7 @@ def code_wrap(s): class DweetTestCase(TransactionTestCase): + def setUp(self): self.client = Client() self.user = User.objects.create(username="user", password="") diff --git a/dwitter/tests/feed/test_feed_views.py b/dwitter/tests/feed/test_feed_views.py index ac635c3b..be924d54 100644 --- a/dwitter/tests/feed/test_feed_views.py +++ b/dwitter/tests/feed/test_feed_views.py @@ -8,7 +8,8 @@ from datetime import timedelta -class DweetFeedTestCase(): # Not inheriting from TestCase, an abstract test class if you will +class DweetFeedTestCase(): # not inheriting from TestCase, an abstract test class if you will + request_factory = RequestFactory() def setUp(self): @@ -49,7 +50,7 @@ def setUp(self): for i in range(random.randrange(0, 5)): filler_dweet.likes.add(users[i]) - # Add comments with some #hashtags + # add comments with some #hashtags Comment.objects.create(id=1, text="comment1 text with #hashtag #test ", posted=now - timedelta(minutes=1), @@ -89,6 +90,7 @@ def test_html_response(self): class HotDweetFeedTests(DweetFeedTestCase, TestCase): + dweetFeed = HotDweetFeed() def test_hot_sort(self): @@ -102,6 +104,7 @@ def test_hot_sort(self): class TopDweetFeedTests(DweetFeedTestCase, TestCase): + dweetFeed = TopDweetFeed() def test_top_sort(self): @@ -115,6 +118,7 @@ def test_top_sort(self): class NewDweetFeedTests(DweetFeedTestCase, TestCase): + dweetFeed = NewDweetFeed() def test_new_sort(self): @@ -127,4 +131,5 @@ def test_new_sort(self): class RandomDweetFeedTests(DweetFeedTestCase, TestCase): + dweetFeed = RandomDweetFeed() diff --git a/dwitter/tests/feed/test_hashtag_feed_views.py b/dwitter/tests/feed/test_hashtag_feed_views.py index 6442ef24..212f1b4f 100644 --- a/dwitter/tests/feed/test_hashtag_feed_views.py +++ b/dwitter/tests/feed/test_hashtag_feed_views.py @@ -10,6 +10,7 @@ class HashtagFeedTestCase(): # Not inheriting from TestCase, an abstract test class if you will + request_factory = RequestFactory() dweetFeed = {} @@ -96,6 +97,7 @@ def test_html_response(self): class TopHashtagFeedTests(HashtagFeedTestCase, TestCase): + dweetFeed = TopHashtagFeed() def test_top_sort(self): @@ -109,6 +111,7 @@ def test_top_sort(self): class NewHashtagFeedTests(HashtagFeedTestCase, TestCase): + dweetFeed = NewHashtagFeed() def test_new_sort(self): diff --git a/dwitter/tests/feed/test_post_views.py b/dwitter/tests/feed/test_post_views.py index f14087a6..5f6f7da8 100644 --- a/dwitter/tests/feed/test_post_views.py +++ b/dwitter/tests/feed/test_post_views.py @@ -6,6 +6,7 @@ class PostDweetTestCase(TransactionTestCase): + def setUp(self): self.client = Client() @@ -77,18 +78,18 @@ def test_post_new_dweet_with_first_comment_with_hashtag(self): def test_too_long_dweet_post(self): user = self.login() - response = self.client.post('/dweet', {'code': 'test code that is way too long,' + + response = self.client.post('/dweet', {'code': 'Test code that is way too long,' + 'wow this looks long in code.' + 'We could fit so much in here.' + - 'oh wow. mooooooooooooooooar text.' + - 'Getting there.' + - 'And BAM tooo long!'}, follow=True) + 'Oh wow. Mooooooooooooooooar text.' + + 'Getting there...' + + 'And BAM! Tooo long!'}, follow=True) self.assertContains(response, "Dweet code too long!", status_code=400) # shorter code should go through! - response = self.client.post('/dweet', {'code': 'test code that is a lot shorter,' + - 'wow this looks long in code.' + - 'And BAM not tooo long!'}, follow=True) + response = self.client.post('/dweet', {'code': 'Test code that is a lot shorter,' + + 'wow, so short...' + + 'And BAM! Not too long!'}, follow=True) self.assertEqual(response.status_code, 200) dweets = Dweet.objects.filter(author=user) @@ -149,18 +150,18 @@ def test_post_dweet_reply_with_first_comment_with_hashtag(self): def test_too_long_dweet_reply(self): user = self.login() - response = self.client.post('/d/1000/reply', {'code': 'test code that is way too long,' + + response = self.client.post('/d/1000/reply', {'code': 'Test code that is way too long,' + 'wow this looks long in code.' + 'We could fit so much in here.' + - 'oh wow. mooooooooooooooooar text.' + - 'Getting there.' + - 'And BAM tooo long!'}, follow=True) + 'Oh wow. Mooooooooooooooooar text.' + + 'Getting there...' + + 'And BAM! Tooo long!'}, follow=True) self.assertContains(response, "Dweet code too long!", status_code=400) # shorter code should go through! - response = self.client.post('/d/1000/reply', {'code': 'test code that is a lot shorter,' + - 'wow this looks long in code.' + - 'And BAM not tooo long!'}, follow=True) + response = self.client.post('/d/1000/reply', {'code': 'Test code that is a lot shorter,' + + 'wow, so short...' + + 'And BAM! Not tooo long!'}, follow=True) self.assertEqual(response.status_code, 200) dweets = Dweet.objects.filter(author=user) diff --git a/dwitter/tests/feed/test_user_feeds.py b/dwitter/tests/feed/test_user_feeds.py index 18fd67bf..9f278fcb 100644 --- a/dwitter/tests/feed/test_user_feeds.py +++ b/dwitter/tests/feed/test_user_feeds.py @@ -9,6 +9,7 @@ class UserFeedTestCase(): # Not inheriting from TestCase, an abstract test class if you will + request_factory = RequestFactory() def setUp(self): @@ -41,7 +42,7 @@ def test_all_dweet_author(self): def test_queryset_size(self): self.dweetFeed.kwargs = {'url_username': self.users[1].username} queryset = self.dweetFeed.get_queryset() - # Each user have 10 dweets from setUp + # Each user has 10 dweets from setUp() self.assertEqual(queryset.count(), 10) def test_annotation(self): @@ -56,18 +57,22 @@ def test_annotation(self): class NewUserFeedTests(UserFeedTestCase, TestCase): + dweetFeed = NewUserFeed() class TopUserFeedTests(UserFeedTestCase, TestCase): + dweetFeed = TopUserFeed() class HotUserFeedTests(UserFeedTestCase, TestCase): + dweetFeed = HotUserFeed() class NewLikedFeedTests(UserFeedTestCase, TestCase): + dweetFeed = NewLikedFeed() def test_queryset_objects(self): diff --git a/dwitter/tests/models/test_comment.py b/dwitter/tests/models/test_comment.py index f8910cd4..28da51da 100644 --- a/dwitter/tests/models/test_comment.py +++ b/dwitter/tests/models/test_comment.py @@ -7,6 +7,7 @@ class DweetTestCase(TestCase): + def setUp(self): user1 = User.objects.create(username="user1", password="") user2 = User.objects.create(username="user2", password="") diff --git a/dwitter/tests/models/test_dweet.py b/dwitter/tests/models/test_dweet.py index 47ef6d96..f3fc6d4e 100644 --- a/dwitter/tests/models/test_dweet.py +++ b/dwitter/tests/models/test_dweet.py @@ -6,6 +6,7 @@ class DweetTestCase(TestCase): + def setUp(self): user1 = User.objects.create(username="user1", password="") user2 = User.objects.create(username="user2", password="") diff --git a/dwitter/tests/models/test_hashtags.py b/dwitter/tests/models/test_hashtags.py index b8918c4c..eae1065b 100644 --- a/dwitter/tests/models/test_hashtags.py +++ b/dwitter/tests/models/test_hashtags.py @@ -6,6 +6,7 @@ class HashtagTestCase(TestCase): + def setUp(self): user1 = User.objects.create(username="user1", password="") self.user2 = User.objects.create(username="user2", password="") @@ -41,17 +42,13 @@ def test_hashtags_created(self): h1 = Hashtag.objects.get(name='hash1') h2 = Hashtag.objects.get(name='hash2') - try: - illegal1 = Hashtag.objects.get(name='1hash') - self.assertEqual(illegal1, True) # should throw an exception! - except: - pass - - try: - illegal2 = Hashtag.objects.get(name='2hash') - self.assertEqual(illegal2, True) # should throw an exception! - except: - pass + # These shouldn't throw any exceptions anymore, + # since hashtags staring with numbers are now allowed + one_hash = Hashtag.objects.get(name='1hash') + self.assertEqual(one_hash, True) + + two_hash = Hashtag.objects.get(name='2hash') + self.assertEqual(two_hash, True) self.assertEqual(h is None, False) self.assertEqual(h1 is None, False) diff --git a/dwitter/tests/templatetags/test_insert_code_blocks.py b/dwitter/tests/templatetags/test_insert_code_blocks.py index 87c4e58c..804b7c16 100644 --- a/dwitter/tests/templatetags/test_insert_code_blocks.py +++ b/dwitter/tests/templatetags/test_insert_code_blocks.py @@ -3,20 +3,35 @@ class DweetTestCase(TestCase): - def test_insert_code_blocks_wraps_stuff_inside_backticks(self): + + def test_insert_code_inserts_code_tags_around_stuff_between_backticks(self): self.assertEqual( 'a', insert_code_blocks('`a`') ) - def test_insert_code_blocks_ignores_backticks(self): + def test_insert_code_bypasses_double_backticks(self): self.assertEqual( 'a=`b`', insert_code_blocks('`a=\`b\``') ) - def test_insert_code_blocks_is_not_greedy_with_multiple_blocks(self): + def test_insert_code_is_not_greedy_with_multiple_blocks(self): self.assertEqual( 'a 1 b 2 c', insert_code_blocks('a `1` b `2` c') ) + + def test_insert_code_removes_anchors(self): + self.assertEqual( + 'My sneaky link here, and also here', + insert_code_blocks('My sneaky link here, and also here') + ) + + def test_insert_code_with_no_code(self): + try: + insert_code_blocks('') + insert_code_blocks('No code here, move along!') + except: + self.fail("insert_code_blocks() threw an exception with a blank block, " + "or a code block with non-code text.") diff --git a/dwitter/tests/templatetags/test_insert_magic_links.py b/dwitter/tests/templatetags/test_insert_magic_links.py index d590fb20..8dfbc8dd 100644 --- a/dwitter/tests/templatetags/test_insert_magic_links.py +++ b/dwitter/tests/templatetags/test_insert_magic_links.py @@ -3,7 +3,8 @@ class DweetTestCase(TestCase): - def test_insert_magic_links_bypasses_html(self): + + def test_insert_magic_bypasses_html(self): self.assertEqual( 'prefix

    content

    suffix', insert_magic_links('prefix

    content

    suffix') @@ -11,187 +12,319 @@ def test_insert_magic_links_bypasses_html(self): # user - def test_insert_magic_links_replaces_user_with_valid_characters(self): + def test_insert_magic_replaces_user_valid_characters(self): self.assertEqual( 'u/a1_.@+-', insert_magic_links('u/a1_.@+-') ) - def test_insert_magic_links_bypasses_user_with_invalid_characters(self): + def test_insert_magic_bypasses_user_invalid_characters(self): self.assertEqual( 'u/a1$', - 'u/a1$' + insert_magic_links('u/a1$') ) - def test_insert_magic_links_replaces_standalone_user(self): + def test_insert_magic_replaces_standalone_user(self): self.assertEqual( 'u/a', insert_magic_links('u/a') ) - def test_insert_magic_links_replaces_user_at_start_of_string(self): + def test_insert_magic_replaces_user_at_start_of_string(self): self.assertEqual( 'u/a suffix', insert_magic_links('u/a suffix') ) - def test_insert_magic_links_replaces_user_at_end_of_string(self): + def test_insert_magic_replaces_user_at_end_of_string(self): self.assertEqual( 'prefix u/a', insert_magic_links('prefix u/a') ) - def test_insert_magic_links_replaces_user_at_middle_of_string(self): + def test_insert_magic_replaces_user_at_middle_of_string(self): self.assertEqual( 'prefix u/a suffix', insert_magic_links('prefix u/a suffix') ) - def test_insert_magic_links_bypasses_user_prefixed_by_non_space(self): + def test_insert_magic_bypasses_user_prefixed_by_non_space(self): self.assertEqual( 'prefixu/a suffix', insert_magic_links('prefixu/a suffix') ) - def test_insert_magic_links_bypasses_user_suffixed_by_non_space(self): + def test_insert_magic_bypasses_user_suffixed_by_non_space(self): self.assertEqual( 'prefix u/a/suffix', insert_magic_links('prefix u/a/suffix') ) - def test_insert_magic_links_replaces_user_suffixed_by_slash(self): + def test_insert_magic_replaces_user_prefixed_by_slash(self): self.assertEqual( 'prefix u/a prefix/u/a', insert_magic_links('prefix /u/a prefix/u/a') ) - def test_insert_magic_links_replaces_user_inside_parenthases(self): + def test_insert_magic_replaces_user_inside_parenthases(self): self.assertEqual( '(u/a)', insert_magic_links('(u/a)') ) + # test that username mentions follow the new punctuation rules + def test_user_mention_punctuation(self): + self.assertEqual( + "are you there, u/admin?" + "hello u/ser!" + "check out this comment from u/person:" + "u/1a and " + "u/2b are the top dwitter users" + "u/tobe; or u/nottobe, " + "that is the u/question." + "u/1am1nv4L1D##@$", + insert_magic_links( + "are you there, u/admin?" + "hello u/ser!" + "check out this comment from u/person:" + "u/1a and u/2b are the top dwitter users" + "u/tobe; or u/nottobe, that is the u/question." + "u/1am1nv4L1D##@$" + ) + ) + + # autocrop (https://dwitter.net/d/1 -> d/1) + + def test_insert_magic_autocrops_urls_d(self): + self.assertEqual( + 'd/123', + insert_magic_links('dwitter.net/d/123') + ) + + def test_insert_magic_autocrops_urls_d_https(self): + self.assertEqual( + 'd/123', + insert_magic_links('https://dwitter.net/d/123') + ) + + def test_insert_magic_autocrops_urls_d_www(self): + self.assertEqual( + 'd/123', + insert_magic_links('www.dwitter.net/d/123') + ) + + def test_insert_magic_autocrops_urls_d_https_www(self): + self.assertEqual( + 'd/123', + insert_magic_links('https://www.dwitter.net/d/123') + ) + + def test_insert_magic_autocrops_urls_d_mixed(self): + self.assertEqual( + 'd/123 d/456', + insert_magic_links('dwitter.net/d/123 http://dwitter.net/d/456') + ) + + # autocrop with u/ links + + def test_insert_magic_autocrops_urls_u(self): + self.assertEqual( + 'u/yonatan', + insert_magic_links('dwitter.net/u/yonatan') + ) + + def test_insert_magic_autocrops_urls_u_https(self): + self.assertEqual( + 'u/veubeke', + insert_magic_links('https://dwitter.net/u/veubeke') + ) + + def test_insert_magic_autocrops_urls_u_www(self): + self.assertEqual( + 'u/pavel', + insert_magic_links('www.dwitter.net/u/pavel') + ) + + def test_insert_magic_autocrops_urls_u_https_www(self): + self.assertEqual( + 'u/lionleaf', + insert_magic_links('https://www.dwitter.net/u/lionleaf') + ) + + def test_insert_magic_autocrops_urls_u_mixed(self): + self.assertEqual( + 'u/sigveseb u/aemkei', + insert_magic_links('dwitter.net/u/sigveseb http://dwitter.net/u/aemkei') + ) + + def test_insert_magic_autocrop_with_text(self): + self.assertEqual( + 'Whoa, have you seen the dweets u/sigveseb makes?', + insert_magic_links('Whoa, have you seen the dweets dwitter.net/u/sigveseb makes?') + ) + + # autocrop with http:// - in case someone forgets themselves + + def test_insert_magic_autocrops_urls_http_d(self): + self.assertEqual( + 'd/123', + insert_magic_links('http://dwitter.net/d/123') + ) + + def test_insert_magic_autocrops_urls_http_u(self): + self.assertEqual( + 'u/lionleaf', + insert_magic_links('http://www.dwitter.net/u/lionleaf') + ) + + # prefixed autocrops + + def test_insert_magic_autocrop_bypasses_urls_prefixed(self): + self.assertEqual( + 'prefixhttp://dwitter.net/d/123', + insert_magic_links('prefixhttp://dwitter.net/d/123') + ) + # dweet - def test_insert_magic_links_replaces_dweet_with_valid_characters(self): + def test_insert_magic_replaces_dweet_valid_characters(self): self.assertEqual( 'd/1234567890', insert_magic_links('d/1234567890') ) - def test_insert_magic_links_bypasses_dweet_with_invalid_characters(self): + def test_insert_magic_bypasses_dweet_invalid_characters(self): self.assertEqual( - 'd/1a', - 'd/1a' + 'd/1a$', + insert_magic_links('d/1a$') ) - def test_insert_magic_links_replaces_standalone_dweet(self): + def test_insert_magic_replaces_standalone_dweet(self): self.assertEqual( 'd/1', insert_magic_links('d/1') ) - def test_insert_magic_links_replaces_dweet_at_start_of_string(self): + def test_insert_magic_replaces_dweet_at_start_of_string(self): self.assertEqual( 'd/1 suffix', insert_magic_links('d/1 suffix') ) - def test_insert_magic_links_replaces_dweet_at_end_of_string(self): + def test_insert_magic_replaces_dweet_at_end_of_string(self): self.assertEqual( 'prefix d/1', insert_magic_links('prefix d/1') ) - def test_insert_magic_links_replaces_dweet_at_middle_of_string(self): + def test_insert_magic_replaces_dweet_at_middle_of_string(self): self.assertEqual( 'prefix d/1 suffix', insert_magic_links('prefix d/1 suffix') ) - def test_insert_magic_links_bypasses_dweet_prefixed_by_non_space(self): + def test_insert_magic_bypasses_dweet_prefixed_by_non_space(self): self.assertEqual( 'prefixd/1 suffix', insert_magic_links('prefixd/1 suffix') ) - def test_insert_magic_links_bypasses_dweet_suffixed_by_non_space(self): + def test_insert_magic_bypasses_dweet_suffixed_by_non_space(self): self.assertEqual( 'prefix d/1/suffix', insert_magic_links('prefix d/1/suffix') ) - def test_insert_magic_links_replaces_dweet_suffixed_by_slash(self): + def test_insert_magic_replaces_dweet_prefixed_by_slash(self): self.assertEqual( 'prefix d/1 prefix/d/1', insert_magic_links('prefix /d/1 prefix/d/1') ) - def test_insert_magic_links_replaces_dweet_in_parenthases(self): + def test_insert_magic_replaces_dweet_in_parentheses(self): self.assertEqual( '(d/1)', insert_magic_links('(d/1)') ) + # test that dweet mentions follow the same punctuation rules as usernames, et cetera + def test_insert_magic_dweet_punctuation(self): + self.assertEqual( + 'I love d/123, ' + 'but have you seen d/456?', + insert_magic_links('I love d/123, but have you seen d/456?') + ) + + # hashtag + def test_insert_magic_replaces_basic_hashtag(self): self.assertEqual( '#test', insert_magic_links('#test') ) - def test_insert_magic_replaces_prefix_hashtag(self): + def test_insert_magic_replaces_hashtag_space_prefix(self): self.assertEqual( 'prefix #test', insert_magic_links('prefix #test') ) - def test_insert_magic_replaces_hashtag_prefix_no_space(self): + def test_insert_magic_bypasses_hashtag_prefix_no_space(self): self.assertEqual( - 'prefix#test', + 'prefix#test', insert_magic_links('prefix#test') ) - def test_insert_magic_replaces_hashtag_paren(self): - self.assertEqual( - 'prefix(#test)', - insert_magic_links('prefix(#test)') - ) - def test_insert_magic_replaces_hashtag_underscore(self): self.assertEqual( 'Dwitter is just #amazing_underscore, right?', insert_magic_links('Dwitter is just #amazing_underscore, right?') ) - def test_insert_magic_replaces_hashtag_illegal_hyphen(self): + def test_insert_magic_bypasses_hashtag_illegal_hyphen(self): self.assertEqual( - 'Dwitter is just #amaze-balls, right?', + 'Dwitter is just #amaze-balls, right?', insert_magic_links('Dwitter is just #amaze-balls, right?') ) - def test_insert_magic_hashtag_not_start_with_digit(self): + def test_insert_magic_bypasses_hashtag_no_letters(self): + self.assertEqual( + 'Dwitter is so #1337', + insert_magic_links('Dwitter is so #1337') + ) + + def test_insert_magic_replaces_hashtag_digits_then_letters(self): self.assertEqual( - 'Dwitter is just #1337 or #super1337?', - insert_magic_links('Dwitter is just #1337 or #super1337?') + 'We are the #1337elite', + insert_magic_links('We are the #1337elite') + ) + + def test_insert_magic_hashtag_letters_and_digits(self): + self.assertEqual( + 'Dwitter is like #abc123', + insert_magic_links('Dwitter is like #abc123') ) def test_insert_magic_single_character_hashtag(self): self.assertEqual( - '#s', - insert_magic_links('#s') + '#h', + insert_magic_links('#h') ) self.assertEqual( - '#S', - insert_magic_links('#S') + '#H', + insert_magic_links('#H') ) + + # test that hashtags follow the same punctuation rules as usernames, et cetera + def test_insert_magic_hashtag_punctuation(self): self.assertEqual( - '#1', # Start with digit not legal - insert_magic_links('#1') + '#hash, but also #tag. Not #tag$%', + insert_magic_links('#hash, but also #tag. Not #tag$%') ) # mixed - def test_insert_magic_links_mixed(self): + def test_insert_magic_mixed(self): self.assertEqual( 'u/john remixed ' 'd/123 by ' @@ -199,13 +332,12 @@ def test_insert_magic_links_mixed(self): insert_magic_links('u/john remixed d/123 by /u/jane') ) - def test_insert_magic_links_mixed_hashtag(self): + def test_insert_magic_mixed_hashtag(self): self.assertEqual( '#awesome ' 'u/john remixed ' '#amazing ' 'd/123 by ' - 'u/jane' - '#yey', + 'u/jane#yey', insert_magic_links('#awesome u/john remixed #amazing d/123 by /u/jane#yey') ) diff --git a/dwitter/tests/urls/test_all_urls.py b/dwitter/tests/urls/test_all_urls.py index 01779fd9..6f0307bb 100644 --- a/dwitter/tests/urls/test_all_urls.py +++ b/dwitter/tests/urls/test_all_urls.py @@ -9,7 +9,7 @@ from django.contrib import auth -# Some automatic based on +# Some automatic checks based on # https://stackoverflow.com/questions/14454001/list-all-suburls-and-check-if-broken-in-python class UrlsTest(test.TestCase): @@ -49,7 +49,7 @@ def setUp(self): reply_to=dweet1, author=user2) - def test_logged_in_urls(self): + def test_logged_in_urls(self): self.responses_test(credentials={'username': 'user1', 'password': 'qwertypw'}) def test_guest_urls(self): @@ -63,22 +63,23 @@ def responses_test(self, allowed_http_codes=[200, 302, 405], logout_url="logout" 'dweet_id': '2', 'hashtag_name': 'test'}): """ - Test all pattern in root urlconf and included ones. - Do GET requests only. + Test all patterns in the root urlconf, and included ones. + Send GET requests only. A pattern is skipped if any of the conditions applies: - pattern has no name in urlconf - - pattern expects any positinal parameters + - pattern expects any positional parameters - pattern expects keyword parameters that are not specified in @default_kwargs - If response code is not in @allowed_http_codes, fail the test. - if @credentials dict is specified (e.g. username and password), - login before run tests. - If @logout_url is specified, then check if we accidentally logged out - the client while testing, and login again + If the response code is not in @allowed_http_codes, the test fails. + If a @credentials dictionary is specified (e.g. username and password), + login before running the tests. + If a @logout_url is specified, then check if we haven't accidentally logged out + the client while testing, and login again. Specify @default_kwargs to be used for patterns that expect keyword parameters, e.g. if you specify default_kwargs={'username': 'testuser'}, then for pattern url(r'^accounts/(?P[\.\w-]+)/$' the url /accounts/testuser/ will be tested. - If @quiet=False, print all the urls checked. If status code of the response is not 200, + If @quiet is set to False, print all the urls checked. + If the status code of the response is not 200, print the status code. """ module = importlib.import_module(settings.ROOT_URLCONF)