Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions app/components/SideBar/Right/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,17 @@ const { mutate: followUser } = useFollowMutation();
<Label class="text-md" for="r1">{{
$t('rightsidebar.search-filters.people.any-one')
}}</Label>
<UiRadioGroupItem id="r1" value="anyone" />
<UiRadioGroupItem id="r1" value="anyone" data-cy="search-filter-people-anyone" />
</div>
<div class="flex items-center justify-between">
<Label class="text-md" for="r2">{{
$t('rightsidebar.search-filters.people.you-follow')
}}</Label>
<UiRadioGroupItem id="r2" value="you-follow" />
<UiRadioGroupItem
id="r2"
value="you-follow"
data-cy="search-filter-people-you-follow"
/>
</div>
</UiRadioGroup>
</div>
Expand All @@ -104,6 +108,7 @@ const { mutate: followUser } = useFollowMutation();
v-if="showWhatIsHappening"
:title="$t('rightsidebar.whats-happening.title')"
data-test="whats-happening-card"
data-cy="whats-happening-card"
>
<div
v-if="trendingIsLoading"
Expand All @@ -118,13 +123,21 @@ const { mutate: followUser } = useFollowMutation();
:hashtag="hashtag"
:rank="index"
/>
<UiButton variant="ghost-primary" size="sm" @click="goToExplore">
<UiButton
variant="ghost-primary"
size="sm"
data-cy="show-more-hashtags-button"
@click="goToExplore"
>
{{ $t('rightsidebar.show-more') }}
</UiButton>
</SideBarRightPreviewCard>
<!-- Who to follow -->
<ClientOnly>
<SideBarRightPreviewCard :title="$t('rightsidebar.who-to-follow.title')">
<SideBarRightPreviewCard
:title="$t('rightsidebar.who-to-follow.title')"
data-cy="who-to-follow-card"
>
<UserRow
v-for="item in whoToFollowItems?.pages.flatMap((page) => page.data) || []"
:key="item.username"
Expand Down
7 changes: 4 additions & 3 deletions app/components/explore/Hashtag.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,19 @@ const handleClick = () => {
<NuxtLink
:to="`/search/top?q=${encodeURIComponent(props.hashtag.hashtag)}`"
class="hover:bg-accent flex cursor-pointer flex-col gap-1 px-4 py-2"
data-cy="explore-hashtag-item"
@click="handleClick"
>
<p class="text-muted-foreground text-xs font-semibold">
<p class="text-muted-foreground text-xs font-semibold" data-cy="explore-hashtag-trending-in">
{{
$t('explore.hashtag', {
category: props.hashtag.category,
rank: props.rank + 1,
})
}}
</p>
<p class="text-sm font-bold">{{ props.hashtag.hashtag }}</p>
<p class="text-muted-foreground text-xs font-semibold">
<p class="text-sm font-bold" data-cy="explore-hashtag-text">{{ props.hashtag.hashtag }}</p>
<p class="text-muted-foreground text-xs font-semibold" data-cy="explore-hashtag-nposts">
{{ $n(props.hashtag.tweetsCount, { notation: 'compact' }) }} {{ $t('posts') }}
</p>
</NuxtLink>
Expand Down
1 change: 1 addition & 0 deletions app/components/ui/SearchField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ onMounted(() => {
<NuxtLink
:to="`/search/top?q=${encodeURIComponent(searchQuery)}`"
class="hover:bg-accent flex items-center transition-colors"
data-cy="search-search-for-text"
@click="saveInHistory({ type: 'hashtag', content: searchQuery })"
>
<p class="text-foreground w-full p-4 break-words">
Expand Down
1 change: 1 addition & 0 deletions app/layouts/explore.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const tabs = [
:label="tab.label"
:route="tab.route"
:is-active="$route.path === tab.route"
:data-cy="`explore-${tab.route.split('/')[2]}-tab`"
/>
</Tabs>
</template>
Expand Down
3 changes: 3 additions & 0 deletions app/layouts/notifications.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ const tabs = [
{
label: $t('notifications.tabs.all'),
route: '/notifications',
datacy: 'notifications-all-tab',
},
{
label: $t('notifications.tabs.mentions'),
route: '/notifications/mentions',
datacy: 'notifications-mentions-tab',
},
];
</script>
Expand All @@ -22,6 +24,7 @@ const tabs = [
:label="tab.label"
:route="tab.route"
:is-active="$route.path.toLowerCase() === tab.route"
:data-cy="tab.datacy"
/>
</UiTabs>
</div>
Expand Down
1 change: 1 addition & 0 deletions app/layouts/search.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const tabs = computed(() => {
:label="tab.label"
:route="tab.route"
:is-active="$route.path === tab.path"
:data-cy="`search-${tab.path.split('/')[2]}-tab`"
/>
</Tabs>
</template>
Expand Down
7 changes: 6 additions & 1 deletion app/pages/explore/[tab].vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ watch(
:rank="index"
/>
</div>
<div v-else data-test="no-results" class="mx-auto my-10 max-w-90 px-8 text-start break-words">
<div
v-else
data-test="no-results"
class="mx-auto my-10 max-w-90 px-8 text-start break-words"
data-cy="no-trending-hashtags"
>
<p class="text-[2rem] leading-tight font-black">{{ $t('explore.no-trending-hashtags') }}</p>
</div>
</div>
Expand Down
6 changes: 5 additions & 1 deletion app/pages/explore/for-you.vue
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,11 @@ watch(
</div>

<!-- Tweets Section -->
<div ref="parentRef" class="border-border mx-auto max-w-[700px]">
<div
ref="parentRef"
class="border-border mx-auto max-w-[700px]"
data-cy="explore-for-you-tweets"
>
<ClientOnly>
<div v-if="tweets">
<div
Expand Down
3 changes: 3 additions & 0 deletions app/pages/notifications/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ watch(
<div v-if="notifications">
<div
data-testid="notifications-list"
data-cy="notifications-list"
:style="{
height: `${totalSize}px`,
width: '100%',
Expand All @@ -91,6 +92,7 @@ watch(
:key="notifications[virtualRow.index]?.id || String(virtualRow.key)"
:ref="measureElement"
:data-index="virtualRow.index"
data-cy="notification-item"
>
<component
:is="componentForType(notifications[virtualRow.index]!.type)"
Expand All @@ -102,6 +104,7 @@ watch(
"
:is-seen="notifications[virtualRow.index]!.isSeen"
:tweet="notifications[virtualRow.index]!.tweetSummary?.primaryTweet"
:data-cy="`notification-${notifications[virtualRow.index]!.type.toLowerCase()}`"
@follow="(username: string) => followUser({ username, action: 'follow' })"
@unfollow="(username: string) => followUser({ username, action: 'unfollow' })"
/>
Expand Down
200 changes: 200 additions & 0 deletions cypress/e2e/explore/explore.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
describe('Explore Page', { testIsolation: false }, function () {
let masterUser: { username: string; email: string; password: string };
let slaveUser: { username: string; email: string; password: string };

before(function () {
cy.clearAllCookies();
cy.clearAllLocalStorage();
cy.clearAllSessionStorage();
cy.createTestUser().then((mUser) => {
masterUser = mUser;
cy.createTestUser().then((sUser) => {
slaveUser = sUser;
cy.wrap(mUser).as('masterUser');
cy.wrap(sUser).as('slaveUser');
// Login as master user
cy.login(mUser.email, mUser.password);
cy.loginExternal(sUser.email, sUser.password);
});
});
});
// Restore aliases before each test
beforeEach(function () {
if (masterUser) cy.wrap(masterUser).as('masterUser');
if (slaveUser) cy.wrap(slaveUser).as('slaveUser');
cy.visitAndWaitForHydration('/explore');
});
describe('Navigation and Items', function () {
it('it should show tabs', function () {
cy.get('a[data-cy="explore-for-you-tab"]').should('exist');
cy.get('a[data-cy="explore-trending-tab"]').should('exist');
cy.get('a[data-cy="explore-news-tab"]').should('exist');
cy.get('a[data-cy="explore-sports-tab"]').should('exist');
cy.get('a[data-cy="explore-entertainment-tab"]').should('exist');
});
it('should show who to follow card with functionality', function () {
cy.get('div[data-cy="who-to-follow-card"]').should('exist');
cy.get('div[data-cy="who-to-follow-card"]')
.find('[data-cy="user-row"]')
.its('length')
.should('be.gte', 1);
// Check that follow buttons exist
cy.get('div[data-cy="who-to-follow-card"]').within(() => {
cy.get('button[data-cy="profile-follow-button"]').first().should('exist').click();
cy.get('button[data-cy="profile-unfollow-button"]').first().should('exist');
});
});
it('should show trending and who to follow cards in home page', function () {
cy.visitAndWaitForHydration('/');
cy.get('div[data-cy="whats-happening-card"]').should('exist');
cy.get('div[data-cy="who-to-follow-card"]').should('exist');
cy.get('div[data-cy="who-to-follow-card"]')
.find('[data-cy="user-row"]')
.its('length')
.should('be.gte', 1);
cy.get('div[data-cy="whats-happening-card"]')
.find('[data-cy="explore-hashtag-item"]')
.its('length')
.should('be.gte', 1);
cy.get('div[data-cy="whats-happening-card"]')
.find('button[data-cy="show-more-hashtags-button"]')
.should('exist')
.click();
cy.url().should('include', '/explore/');
});
it('trending tab should be clickable and show hashtags', function () {
cy.get('a[data-cy="explore-trending-tab"]').should('exist').click();
cy.url().should('include', '/explore/trending');
cy.get('[data-cy="explore-hashtag-item"]').its('length').should('be.gte', 1);
});
it('should show for-you tab with hashtags and tweets', function () {
cy.get('a[data-cy="explore-for-you-tab"]').should('exist').click();
cy.url().should('include', '/explore/for-you');
cy.get('[data-cy="explore-hashtag-item"]').its('length').should('be.gte', 1);
cy.get('[data-cy="tweet"]').its('length').should('be.gte', 1);
});
it('should navigate to Search when clicking on hashtag item', function () {
cy.get('[data-cy="explore-hashtag-item"]')
.first()
.within(() => {
cy.get('p[data-cy="explore-hashtag-text"]')
.invoke('text')
.then((text) => {
cy.get('p[data-cy="explore-hashtag-text"]').click();
cy.url().should('include', `/search/top?q=${text}`);
});
});
});
it('news, sports, entertainment tabs should either show trends or nothing', function () {
const tabs = [
{ name: 'news', selector: 'a[data-cy="explore-news-tab"]' },
{ name: 'sports', selector: 'a[data-cy="explore-sports-tab"]' },
{ name: 'entertainment', selector: 'a[data-cy="explore-entertainment-tab"]' },
];
tabs.forEach((tab) => {
cy.get(tab.selector).should('exist').click();
cy.url().should('include', `/explore/${tab.name}`);
// either div[data-cy="no-trending-hashtags"] exists or there are hashtag items
cy.get('[data-cy="explore-hashtag-item"], div[data-cy="no-trending-hashtags"]')
.its('length')
.should('be.gte', 1);
});
});
});
describe('Search Functionality', function () {
describe('Profile Search Functionality', () => {
it('should search for profiles correctly', function () {
cy.get('[data-cy="search-input"]').type(this.slaveUser.username);
// Check that search results contain the slave user (it might show multiple results)
cy.get('[data-cy="search-user-result"]').should('contain.text', this.slaveUser.username);
// Click on the slave user result
cy.get('[data-cy="search-user-result"]').contains(this.slaveUser.username).click();
cy.url().should('include', `/profile/${this.slaveUser.username}`);
cy.get('[data-cy="profile-user-name"]').should('contain.text', this.slaveUser.username);
});

it('should show go to profile option for valid usernames', function () {
cy.get('[data-cy="search-input"]').type('gelgel');
cy.get('[data-cy="search-go-to-profile"]')
.should('exist')
.should('contain.text', 'gelgel')
.click();
cy.url().should('include', `/profile/gelgel`);
cy.get('[data-cy="profile-user-name"]').should('contain.text', 'gelgel');
});
it('should allow "Search for [user]" (top and people)', function () {
cy.get('[data-cy="search-input"]').clear().type(this.slaveUser.username);
cy.get('a[data-cy="search-search-for-text"]').should('contain.text', `Search For`).click();
// Check that search results contain the slave user
cy.get('[data-cy="main-content"]').within(() => {
cy.get('[data-cy="user-row"]').should('contain.text', this.slaveUser.username);
});
// Now click on People tab
cy.get('a[data-cy="search-people-tab"]').should('exist').click();
// Check that search results in People tab also contain the slave user
cy.get('[data-cy="main-content"]').within(() => {
cy.get('[data-cy="user-row"]').should('contain.text', this.slaveUser.username);
});
});
});
describe('Tweets Search Functionality', () => {
it('should search for tweets correctly (top and latest)', function () {
const timestamp = Date.now();
const tweetContent = `Unique tweet content for search test ${timestamp}`;
cy.postTweet(tweetContent, [], null, null, true).then(() => {
cy.get('[data-cy="search-input"]')
.clear()
.type(`Unique tweet content for search test ${timestamp}`);
cy.get('a[data-cy="search-search-for-text"]')
.should('contain.text', `Search For`)
.click();
// Check that search results contain the tweet content
cy.get('[data-cy="tweet-content"]').should('contain.text', tweetContent);
// Now click on Latest tab
cy.get('a[data-cy="search-latest-tab"]').should('exist').click();
// Check that search results in Latest tab also contain the tweet content
cy.get('[data-cy="tweet-content"]').should('contain.text', tweetContent);
});
});
it('should show results from followed users', function () {
const timestamp = Date.now();
const tweetContent = `Followed user tweet content ${timestamp}`;
cy.postTweet(tweetContent, [], null, null, true).then(() => {
cy.get('[data-cy="search-input"]')
.clear()
.type(`Unique tweet content for search test ${timestamp}`);
cy.get('a[data-cy="search-search-for-text"]')
.should('contain.text', `Search For`)
.click();
cy.get('button[data-cy="search-filter-people-you-follow"]').should('exist').click();
// Check that search results are empty since master user does not follow slave user yet
cy.get('[data-cy="tweet-content"]').should('not.exist');
// Now follow the slave user
cy.followUser(this.slaveUser.username, true);
const currentURL = cy.url();
currentURL.then((url) => {
cy.visit(url);
cy.get('button[data-cy="search-filter-people-you-follow"]').should('exist').click();
// Check that search results now contain the tweet content
cy.get('[data-cy="tweet-content"]').should('contain.text', tweetContent);
});
});
});
it('should show results for tweets with media', function () {
const timestamp = Date.now();
const tweetContent = `Media tweet content ${timestamp}`;
cy.uploadMedia('profile/banner.jpg', 'tweets', true).then((media) => {
cy.postTweet(tweetContent, [media.id], undefined, undefined, true).then(() => {
cy.get('[data-cy="search-input"]').clear().type(`Media tweet content ${timestamp}`);
cy.get('a[data-cy="search-search-for-text"]')
.should('contain.text', `Search For`)
.click();
// switch to Media tab
cy.get('a[data-cy="search-media-tab"]').should('exist').click();
cy.get('[data-cy="thumbnail-image"]').should('exist');
});
});
});
});
});
});
Loading
Loading