Skip to content

Commit ffa1d95

Browse files
authored
Create search view (#30)
1 parent 379b3ab commit ffa1d95

File tree

9 files changed

+332
-6
lines changed

9 files changed

+332
-6
lines changed

frontend/cypress.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
"defaultCommandTimeout": 10000,
55
"env": {
66
"user1": {
7-
"fullName": "Superuser",
8-
"username": "admin",
7+
"fullName": "Bob Dob",
8+
"username": "bobbydobby",
99
"password": "secret",
1010
"email": "[email protected]"
1111
},
1212
"user2": {
13-
"fullName": "Superuser2",
14-
"username": "admin2",
13+
"fullName": "Alice Malice",
14+
"username": "malitaa",
1515
"password": "secret",
1616
"email": "[email protected]"
1717
}

frontend/cypress/integration/comments.spec.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ describe('commenting on a post', () => {
4545
});
4646

4747
it('user can reply to a comment', () => {
48+
const user = Cypress.env('user1');
4849
const commentText = 'This is a test comment';
4950

5051
cy.get('textarea').type(commentText);
@@ -67,7 +68,7 @@ describe('commenting on a post', () => {
6768
.should('be.visible')
6869
.click();
6970

70-
cy.contains(`@admin ${replyText}`).should('be.visible');
71+
cy.contains(`@${user.username} ${replyText}`).should('be.visible');
7172
});
7273

7374
it('when post has a comment, user can see the comment count link', () => {
+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
beforeEach(() => {
2+
const user1 = Cypress.env('user1');
3+
const user2 = Cypress.env('user2');
4+
5+
cy.request('POST', 'http://localhost:3001/api/test/reset');
6+
7+
cy.createUser(user1);
8+
cy.createUser(user2);
9+
10+
cy.login({ username: user1.username, password: user1.password });
11+
});
12+
13+
describe('navigating to search page', () => {
14+
it('user can navigate to search page on desktop', () => {
15+
cy.get('[data-cy=desktop-search-btn]').click();
16+
17+
cy.url().should('include', '/search');
18+
cy.get('input[placeholder="Search"]').should('be.visible');
19+
});
20+
21+
it('user can navigate to search page on mobile', () => {
22+
cy.viewport('iphone-6');
23+
cy.get('[data-cy=mobile-search-btn]').click();
24+
25+
cy.url().should('include', '/search');
26+
cy.get('input[placeholder="Search"]').should('be.visible');
27+
});
28+
});
29+
30+
describe('searching for users', () => {
31+
beforeEach(() => {
32+
cy.visit('http://localhost:3000/search');
33+
});
34+
35+
it('displays no results if input is empty', () => {
36+
const user1 = Cypress.env('user1');
37+
const user2 = Cypress.env('user2');
38+
39+
cy.contains(user1.username).should('not.be.exist');
40+
cy.contains(user2.username).should('not.be.exist');
41+
42+
cy.get('input[placeholder="Search"]').type('a').clear();
43+
44+
cy.contains(user1.username).should('not.be.exist');
45+
cy.contains(user2.username).should('not.be.exist');
46+
});
47+
48+
it('user can search for users by username', () => {
49+
const user1 = Cypress.env('user1');
50+
const user2 = Cypress.env('user2');
51+
52+
cy.contains(user1.username).should('not.be.exist');
53+
cy.contains(user2.username).should('not.be.exist');
54+
55+
cy.get('input[placeholder="Search"]').type(user1.username);
56+
57+
cy.contains(user1.username).should('be.visible');
58+
cy.contains(user2.username).should('not.be.exist');
59+
60+
cy.get('input[placeholder="Search"]').clear().type(user2.username);
61+
62+
cy.contains(user1.username).should('not.be.exist');
63+
cy.contains(user2.username).should('be.visible');
64+
});
65+
66+
it('user can search for users by full name', () => {
67+
const user1 = Cypress.env('user1');
68+
const user2 = Cypress.env('user2');
69+
70+
cy.contains(user1.fullName).should('not.be.exist');
71+
cy.contains(user2.fullName).should('not.be.exist');
72+
73+
cy.get('input[placeholder="Search"]').type(user1.fullName);
74+
75+
cy.contains(user1.fullName).should('be.visible');
76+
cy.contains(user2.fullName).should('not.be.exist');
77+
78+
cy.get('input[placeholder="Search"]').clear().type(user2.fullName);
79+
80+
cy.contains(user1.fullName).should('not.be.exist');
81+
cy.contains(user2.fullName).should('be.visible');
82+
});
83+
84+
it('user can search for users by username case-insensitively', () => {
85+
const user1 = Cypress.env('user1');
86+
87+
cy.contains(user1.username).should('not.be.exist');
88+
89+
cy.get('input[placeholder="Search"]').type(user1.username.toUpperCase());
90+
91+
cy.contains(user1.username).should('be.visible');
92+
});
93+
94+
it('user can search for users by full name case-insensitively', () => {
95+
const user1 = Cypress.env('user1');
96+
97+
cy.contains(user1.fullName).should('not.be.exist');
98+
99+
cy.get('input[placeholder="Search"]').type(user1.fullName.toUpperCase());
100+
101+
cy.contains(user1.fullName).should('be.visible');
102+
});
103+
104+
it('user can search for users by partial username', () => {
105+
const user1 = Cypress.env('user1');
106+
107+
cy.contains(user1.username).should('not.be.exist');
108+
109+
cy.get('input[placeholder="Search"]').type(user1.username.slice(0, 3));
110+
111+
cy.contains(user1.username).should('be.visible');
112+
});
113+
114+
it('user can search for users by partial full name', () => {
115+
const user1 = Cypress.env('user1');
116+
117+
cy.contains(user1.fullName).should('not.be.exist');
118+
119+
cy.get('input[placeholder="Search"]').type(user1.fullName.slice(0, 3));
120+
121+
cy.contains(user1.fullName).should('be.visible');
122+
});
123+
124+
it('clicking on a user navigates to their profile', () => {
125+
const user1 = Cypress.env('user1');
126+
127+
cy.get('input[placeholder="Search"]').type(user1.username);
128+
cy.contains(user1.username).click();
129+
130+
cy.url().should('include', `/${user1.username}`);
131+
});
132+
});

frontend/src/app/App.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { useAppSelector } from '../common/hooks/selector-dispatch-hooks';
3131
import { selectAccessToken } from '../features/auth/authSlice';
3232
import { type NotificationType, type NotificationCount } from './types';
3333
import NotificationsView from '../features/notifications/NotificationsView/NotificationsView';
34+
import SearchView from '../features/users/SearchView/SearchView';
3435

3536
interface LocationState {
3637
background: string,
@@ -212,6 +213,14 @@ function App() {
212213
</RequireAuth>
213214
)}
214215
/>
216+
<Route
217+
path="/search"
218+
element={(
219+
<RequireAuth>
220+
<SearchView />
221+
</RequireAuth>
222+
)}
223+
/>
215224
</Routes>
216225
</>
217226
);

frontend/src/common/components/Navbars/BottomNavbar/BottomNavbar.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ function BottomNavBar({ user, notifications }: BottomNavBarProps) {
5151
</Link>
5252

5353
<Link
54-
to="/"
54+
to="/search"
55+
data-cy="mobile-search-btn"
5556
>
5657
<IconSearch
5758
size={30}

frontend/src/common/components/Navbars/DesktopNavbar/DesktopNavbar.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
IconSquarePlus,
55
IconBrandMessenger,
66
IconHeart,
7+
IconSearch,
78
} from '@tabler/icons-react';
89
import { Link } from 'react-router-dom';
910
import useStyles from './DesktopNavbar.styles';
@@ -78,6 +79,17 @@ function DesktopNavbar({ displayOnMobile, notifications }: DesktopNavbarProps) {
7879
/>
7980
</Link>
8081

82+
<Link
83+
to="/search"
84+
data-cy="desktop-search-btn"
85+
>
86+
<IconSearch
87+
size={30}
88+
strokeWidth={2}
89+
color="black"
90+
/>
91+
</Link>
92+
8193
<UnstyledButton data-cy="desktop-upload-btn">
8294
<label htmlFor="postImageUpload">
8395
<IconSquarePlus

frontend/src/features/users/FollowingFollowersView/FollowingFollowersView.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ function FollowingFollowersView() {
5555
root: classes.searchInputRoot,
5656
}}
5757
onChange={(e) => setFilter(e.currentTarget.value)}
58+
value={filter}
5859
/>
5960

6061
<UsersPreviewList users={newFilteredUsers} />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { createStyles } from '@mantine/core';
2+
3+
export default createStyles((theme) => ({
4+
container: {
5+
[`@media (min-width: ${theme.breakpoints.md}px)`]: {
6+
padding: 0,
7+
paddingBottom: 10,
8+
},
9+
},
10+
inputContainer: {
11+
padding: '6px 16px',
12+
borderBottom: `1px solid ${theme.colors.gray[4]}`,
13+
},
14+
searchInput: {
15+
paddingLeft: '25px !important',
16+
'&::placeholder': {
17+
color: '#737373',
18+
},
19+
border: '1px solid #737373',
20+
},
21+
searchInputRoot: {
22+
marginBottom: 10,
23+
24+
[`@media (min-width: ${theme.breakpoints.md}px)`]: {
25+
position: 'sticky',
26+
zIndex: 500,
27+
padding: 10,
28+
top: 0,
29+
borderBottom: `1px solid ${theme.colors.gray[4]}`,
30+
backgroundColor: 'white',
31+
marginBottom: 5,
32+
},
33+
},
34+
text: {
35+
lineHeight: 0,
36+
},
37+
name: {
38+
color: '#737373',
39+
},
40+
link: {
41+
textDecoration: 'none',
42+
color: 'inherit',
43+
display: 'block',
44+
45+
'&:not(:last-of-type)': {
46+
marginBottom: 10,
47+
},
48+
49+
'&:active': {
50+
opacity: 0.5,
51+
},
52+
53+
'&:hover': {
54+
backgroundColor: '#f5f5f5',
55+
},
56+
padding: '0 10px',
57+
},
58+
}));

0 commit comments

Comments
 (0)