diff --git a/.gitignore b/.gitignore index 0af7e4c..6938093 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ nitter *.html -*.db \ No newline at end of file +*.db +tests/__pycache__ \ No newline at end of file diff --git a/tests/base.py b/tests/base.py new file mode 100644 index 0000000..6579277 --- /dev/null +++ b/tests/base.py @@ -0,0 +1,38 @@ +from seleniumbase import BaseCase + + +class Tweet(object): + def __init__(self, tweet=''): + namerow = tweet + 'div.media-heading > div > .fullname-and-username > ' + self.fullname = namerow + '.fullname' + self.username = namerow + '.username' + self.date = tweet + 'div.media-heading > div > .heading-right' + self.text = tweet + '.status-content-wrapper > .status-content.media-body' + + +class Profile(object): + fullname = '.profile-card-fullname' + username = '.profile-card-username' + bio = '.profile-bio' + protected = '.protected-icon' + verified = '.verified-icon' + + +class BaseTestCase(BaseCase): + def setUp(self): + super(BaseTestCase, self).setUp() + + def tearDown(self): + super(BaseTestCase, self).tearDown() + + def open_nitter(self, page=''): + self.open(f'http://localhost:5000/{page}') + + def search_username(self, username): + self.open_nitter() + self.update_text('input', username) + self.submit('form') + + +def get_timeline_tweet(num=1): + return Tweet(f'#tweets > div:nth-child({num}) > div > div ') diff --git a/tests/test_profile.py b/tests/test_profile.py new file mode 100644 index 0000000..7c7427c --- /dev/null +++ b/tests/test_profile.py @@ -0,0 +1,46 @@ +from base import BaseTestCase, Profile + + +class TestProfile(BaseTestCase): + def test_data(self): + self.open_nitter('mobile_test') + self.assert_exact_text('Test account', Profile.fullname) + self.assert_exact_text('@mobile_test', Profile.username) + self.assert_exact_text('Test Account. test test Testing username with @mobile_test_2 and a #hashtag', + Profile.bio) + + self.open_nitter('mobile_test_2') + self.assert_exact_text('mobile test 2', Profile.fullname) + self.assert_exact_text('@mobile_test_2', Profile.username) + self.assert_element_not_visible(Profile.bio) + + def test_verified(self): + self.open_nitter('jack') + self.assert_element_visible(Profile.verified) + + self.open_nitter('elonmusk') + self.assert_element_visible(Profile.verified) + + def test_protected(self): + self.open_nitter('mobile_test_7') + self.assert_element_visible(Profile.protected) + self.assert_exact_text('mobile test 7', Profile.fullname) + self.assert_exact_text('@mobile_test_7', Profile.username) + self.assert_text('Tweets are protected') + + self.open_nitter('poop') + self.assert_element_visible(Profile.protected) + self.assert_exact_text('Randy', Profile.fullname) + self.assert_exact_text('@Poop', Profile.username) + self.assert_text('Social media fanatic.', Profile.bio) + self.assert_text('Tweets are protected') + + def test_invalid_username(self): + for p in ['test', 'thisprofiledoesntexist', '%']: + self.open_nitter(p) + self.assert_text(f'User "{p}" not found') + + def test_suspended(self): + # TODO: detect suspended + self.open_nitter('test') + self.assert_text(f'User "test" not found') diff --git a/tests/test_search.py b/tests/test_search.py new file mode 100644 index 0000000..6bb24d9 --- /dev/null +++ b/tests/test_search.py @@ -0,0 +1,10 @@ +from base import BaseTestCase + + +class TestSearch(BaseTestCase): + def test_username_search(self): + self.search_username('mobile_test') + self.assert_text('@mobile_test') + + self.search_username('mobile_test_2') + self.assert_text('@mobile_test_2') diff --git a/tests/test_tweet.py b/tests/test_tweet.py new file mode 100644 index 0000000..662799d --- /dev/null +++ b/tests/test_tweet.py @@ -0,0 +1,103 @@ +from base import BaseTestCase, Tweet, get_timeline_tweet + +# image = tweet + 'div.attachments.media-body > div > div > a > div > img' +# self.assert_true(self.get_image_url(image).split('/')[0] == 'http') +class TweetInfo(): + def __init__(self, index, fullname, username, date, text): + self.index = index + self.fullname = fullname + self.username = username + self.date = date + self.text = text + +timeline_tweets = [ + TweetInfo(1, 'Test account', 'mobile_test', '10 Aug 2016', + '.'), + + TweetInfo(3, 'Test account', 'mobile_test', '3 Mar 2016', + 'LIVE on #Periscope pscp.tv/w/aadiTzF6dkVOTXZSbX…'), + + TweetInfo(6, 'mobile test 2', 'mobile_test_2', '1 Oct 2014', + 'Testing. One two three four. Test.') +] + +status_tweets = [ + TweetInfo(20, 'jack 🌍🌏🌎', 'jack', '21 Mar 2006', + 'just setting up my twttr'), + + TweetInfo(134849778302464000, 'The Twoffice', 'TheTwoffice', '10 Nov 2011', + 'test'), + + TweetInfo(105685475985080322, 'The Twoffice', 'TheTwoffice', '22 Aug 2011', + 'regular tweet'), + + TweetInfo(572593440719912960, 'Test account', 'mobile_test', '2 Mar 2015', + 'testing test') +] + +invalid_tweets = [ + 'mobile_test/status/120938109238', + 'TheTwoffice/status/8931928312' +] + +multiline_tweets = [ + TweetInfo(1142904127594401797, '', 'hot_pengu', '', + """ +New tileset, dust effects, background. The 'sea' has per-line parallax and wavey fx which we think is really cool even tho u didn't notice 🐶. code: +@exelotl + #pixelart #gbadev #gba #indiedev"""), + + TweetInfo(400897186990284800, '', 'mobile_test_3', '', + """ +♔ + KEEP + CALM + AND +CLICHÉ + ON""") +] + +class TestTweet(BaseTestCase): + def test_timeline(self): + for info in timeline_tweets: + self.open_nitter(f'{info.username}') + tweet = get_timeline_tweet(info.index) + self.assert_exact_text(info.fullname, tweet.fullname) + self.assert_exact_text('@' + info.username, tweet.username) + self.assert_exact_text(info.date, tweet.date) + self.assert_text(info.text, tweet.text) + + def test_status(self): + tweet = Tweet() + for info in status_tweets: + self.open_nitter(f'{info.username}/status/{info.index}') + self.assert_exact_text(info.fullname, tweet.fullname) + self.assert_exact_text('@' + info.username, tweet.username) + self.assert_exact_text(info.date, tweet.date) + self.assert_text(info.text, tweet.text) + + def test_multiline_formatting(self): + for info in multiline_tweets: + self.open_nitter(f'{info.username}/status/{info.index}') + self.assert_text(info.text.strip('\n'), '.main-tweet') + + def test_emojis(self): + self.open_nitter('Tesla/status/1134850442511257600') + self.assert_text('🌈❤️🧡💛💚💙💜', '.main-tweet') + + def test_links(self): + self.open_nitter('nim_lang/status/1110499584852353024') + self.assert_text('nim-lang.org/araq/ownedrefs.…', '.main-tweet') + self.assert_text('news.ycombinator.com/item?id…', '.main-tweet') + self.assert_text('old.reddit.com/r/programming…', '.main-tweet') + + self.open_nitter('nim_lang/status/1125887775151140864') + self.assert_text('en.wikipedia.org/wiki/Nim_(p…)', '.main-tweet') + + self.open_nitter('hiankun_taioan/status/1086916335215341570') + self.assert_text('(hackernoon.com/interview-wit…)', '.main-tweet') + + def test_invalid_id(self): + for tweet in invalid_tweets: + self.open_nitter(tweet) + self.assert_text('Tweet not found', '.error-panel')