Merge remote-tracking branch 'origin/main'

This commit is contained in:
MartinBraquet
2026-05-29 02:18:43 +02:00
26 changed files with 707 additions and 66 deletions

View File

@@ -213,7 +213,7 @@ export const MBTI_CHOICES = {
ESTP: 'estp',
ESFJ: 'esfj',
ESFP: 'esfp',
}
} as const
// MBTI type name mapping
export const MBTI_TYPE_NAMES: Record<string, string> = {

View File

@@ -0,0 +1,33 @@
import {Browser} from '@playwright/test'
import {App} from '../web/pages/app'
export class ContextManager {
private contexts: Map<string, App> = new Map()
constructor(private browser: Browser) {}
async createContext(customName?: string): Promise<App> {
const name = customName ?? crypto.randomUUID().slice(0, 6)
const existing = this.contexts.get(name)
// Return the existing one instead of closing it?
if (existing) await existing.page.context().close()
const context = await this.browser.newContext()
const page = await context.newPage()
const app = new App(page)
this.contexts.set(name, app)
return app
}
getContext(name: string): App | undefined {
return this.contexts.get(name)
}
async closeAll(): Promise<void> {
for (const app of this.contexts.values()) {
await app.page.context().close()
}
this.contexts.clear()
}
}

View File

@@ -37,6 +37,36 @@ export async function findUser(idToken: string) {
}
}
export async function sendVerificationEmail(idToken: string) {
await axios.post(`${config.FIREBASE_URL.BASE}${config.FIREBASE_URL.SEND_EMAIL_VERIFICATION}`, {
requestType: 'VERIFY_EMAIL',
idToken,
})
}
export async function getOobCode(oobCodes: any[], email: string) {
return oobCodes.find((item) => item.email.toLowerCase() === email.toLowerCase())?.oobCode
}
export async function verifyEmail(email: string, password: string) {
try {
const loginInfo = await firebaseLoginEmailPassword(email, password)
await sendVerificationEmail(loginInfo.data.idToken)
const oobResponse = await axios.get(`${config.FIREBASE_URL.FIREBASE_EMULATOR_API}`)
const oobCode = await getOobCode(oobResponse.data.oobCodes, email)
if (!oobCode) throw new Error(`No verification OOB code found for email: ${email}`)
const response = await axios.post(
`${config.FIREBASE_URL.BASE}${config.FIREBASE_URL.CONFIRM_EMAIL_VERIFICATION}`,
{
oobCode,
},
)
} catch (err: any) {
console.log(err)
throw err
}
}
export async function firebaseSignUp(email: string, password: string) {
try {
const response = await axios.post(`${config.FIREBASE_URL.BASE}${config.FIREBASE_URL.SIGNUP}`, {

View File

@@ -3,7 +3,13 @@ import {createSomeNotifications} from 'shared/create-notification'
import {createSupabaseDirectClient} from 'shared/supabase/init'
import {insert} from 'shared/supabase/utils'
import {seedUser} from './seedDatabase'
import {
seedUser,
TEST_USER_DISPLAY_NAME,
TEST_USER_EMAIL,
TEST_USER_PASSWORD,
TEST_USER_USERNAME,
} from './seedDatabase'
async function seedCompatibilityPrompts(userId: string | null = null) {
// Need some prompts to prevent the onboarding from stopping once it reaches them (just after profile creation)
@@ -59,6 +65,15 @@ type ProfileType = 'basic' | 'medium' | 'full'
}
}
// Used in some tests that require interaction with a permanent user
await seedUser(
TEST_USER_EMAIL,
TEST_USER_PASSWORD,
'full',
TEST_USER_DISPLAY_NAME,
TEST_USER_USERNAME,
)
await seedCompatibilityPrompts()
await seedNotifications()

View File

@@ -9,7 +9,12 @@ import {insert} from 'shared/supabase/utils'
import {getUser} from 'shared/utils'
import UserAccountInformationForSeeding from '../backend/utils/userInformation'
import {firebaseSignUp} from './firebaseUtils'
import {firebaseSignUp, verifyEmail} from './firebaseUtils'
export const TEST_USER_EMAIL = 'user@compass.test'
export const TEST_USER_PASSWORD = 'pass'
export const TEST_USER_DISPLAY_NAME = 'Test User'
export const TEST_USER_USERNAME = 'TestUser'
/**
* Function used to populate the database with profiles.
@@ -151,6 +156,7 @@ export async function seedUser(
profileType?: string | undefined,
displayName?: string | undefined,
userName?: string | undefined,
verifyUserEmail?: boolean,
) {
const userInfo = new UserAccountInformationForSeeding()
if (email) userInfo.email = email
@@ -162,4 +168,5 @@ export async function seedUser(
// Fall back to the pre-generated faker id when Firebase is unreachable
const created = await seedDbUser(userInfo, profileType ?? 'full')
if (created) debug('User created in Supabase:', userInfo.email)
if (verifyUserEmail) await verifyEmail(userInfo.email, userInfo.password)
}

View File

@@ -2,10 +2,13 @@ export const config = {
BASE_URL: 'http://localhost:3000',
FIREBASE_URL: {
BASE: 'http://localhost:9099/identitytoolkit.googleapis.com/v1',
FIREBASE_EMULATOR_API: 'http://localhost:9099/emulator/v1/projects/compass-57c3c/oobCodes',
SIGNUP: '/accounts:signUp?key=fake-api-key',
SIGN_IN_PASSWORD: '/accounts:signInWithPassword?key=fake-api-key',
ACCOUNT_LOOKUP: '/accounts:lookup?key=fake-api-key',
DELETE: '/accounts:delete?key=fake-api-key',
SEND_EMAIL_VERIFICATION: '/accounts:sendOobCode?key=fake-api-key',
CONFIRM_EMAIL_VERIFICATION: '/accounts:update?key=fake-api-key',
},
USERS: {
DEV_1: {

View File

@@ -7,7 +7,9 @@ import {deleteUser} from '../utils/deleteUser'
export const test = base.extend<{
app: App
dev_one_account: UserAccountInformation
devOneAccount: UserAccountInformation
devTwoAccount: UserAccountInformation
specAccount: UserAccountInformation
fakerAccount: UserAccountInformation
googleAccountOne: UserAccountInformation
googleAccountTwo: UserAccountInformation
@@ -17,6 +19,7 @@ export const test = base.extend<{
app: async ({page}, use) => {
const appPage = new App(page)
await use(appPage)
await appPage.contextManager?.closeAll()
},
signedInAccount: async ({app}: {app: App}, use) => {
const account = testAccounts.faker_account()
@@ -26,6 +29,7 @@ export const test = base.extend<{
undefined,
account.display_name,
account.username,
true,
)
await app.signinWithEmail(account)
await use(account)
@@ -39,6 +43,46 @@ export const test = base.extend<{
undefined,
account.display_name,
account.username,
true,
)
await use(account)
await deleteUser('Email/Password', account)
},
devOneAccount: async ({}, use) => {
const account = testAccounts.dev_one_account()
await seedUser(
account.email,
account.password,
undefined,
account.display_name,
account.username,
true,
)
await use(account)
await deleteUser('Email/Password', account)
},
devTwoAccount: async ({}, use) => {
const account = testAccounts.dev_two_account()
await seedUser(
account.email,
account.password,
undefined,
account.display_name,
account.username,
true,
)
await use(account)
await deleteUser('Email/Password', account)
},
specAccount: async ({}, use) => {
const account = testAccounts.spec_account()
await seedUser(
account.email,
account.password,
undefined,
account.display_name,
account.username,
true,
)
await use(account)
await deleteUser('Email/Password', account)

View File

@@ -1,17 +1,19 @@
import {Page} from '@playwright/test'
import {BrowserContext, Page} from '@playwright/test'
import {ContextManager} from '../../utils/contextManager'
import {UserAccountInformation} from '../utils/accountInformation'
import {AuthPage} from './authPage'
import {CompatibilityPage} from './compatibilityPage'
import {HomePage} from './homePage'
import {MessagesPage} from './messagesPage'
import {NotificationPage} from './notificationsPage'
import {OnboardingPage} from './onboardingPage'
import {OrganizationPage} from './organizationPage'
import {PeoplePage} from './peoplePage'
import {ProfilePage} from './profilePage'
import {SettingsPage} from './settingsPage'
import {SignUpPage} from './signUpPage'
import {SocialPage} from './socialPage'
import {PeoplePage} from './peoplePage'
import {NotificationPage} from './notificationsPage'
export class App {
readonly auth: AuthPage
@@ -25,6 +27,9 @@ export class App {
readonly social: SocialPage
readonly people: PeoplePage
readonly notifs: NotificationPage
readonly messages: MessagesPage
readonly contextManager: ContextManager
readonly context: BrowserContext
constructor(public readonly page: Page) {
this.auth = new AuthPage(page)
@@ -38,6 +43,12 @@ export class App {
this.social = new SocialPage(page)
this.people = new PeoplePage(page)
this.notifs = new NotificationPage(page)
this.messages = new MessagesPage(page)
this.context = page.context()
const browser = page.context().browser()
if (!browser) throw new Error('Could not get Browser from page.context().browser()')
this.contextManager = new ContextManager(browser)
}
async deleteProfileFromSettings() {

View File

@@ -0,0 +1,108 @@
import {expect, Locator, Page} from '@playwright/test'
import {sleep} from 'common/util/time'
export class MessagesPage {
private readonly messagesPageHeader: Locator
private readonly messagesTable: Locator
private readonly messagesRow: Locator
private readonly messagesUsername: Locator
private readonly messagesTimestamp: Locator
private readonly newMessageButton: Locator
private readonly newMessageSearchUsers: Locator
private readonly newMessageSearchResults: Locator
private readonly newMessageSearchCreateButton: Locator
private readonly newMessageStart: Locator
private readonly messageInput: Locator
private readonly messageSubmit: Locator
private readonly conversation: Locator
private readonly conversationMessage: Locator
constructor(public readonly page: Page) {
this.messagesPageHeader = page.getByRole('heading', {name: 'Messages'})
this.messagesTable = page.getByTestId('messages-table')
this.messagesRow = page.getByTestId('messages-row')
this.messagesUsername = page.getByTestId('messages-username')
this.messagesTimestamp = page.getByTestId('messages-timestamp')
this.newMessageButton = page.getByRole('button', {name: 'New Message'})
this.newMessageSearchUsers = page.getByRole('textbox', {name: 'Search users...'})
this.newMessageSearchResults = page.getByTestId('search-results')
this.newMessageSearchCreateButton = page.getByRole('button', {name: 'Create'})
this.newMessageStart = page.getByText('No messages yet.', {exact: true})
this.messageInput = page.locator('.tiptap')
this.messageSubmit = page.getByTestId('conversation-message-submit')
this.conversation = page.getByTestId('conversation')
this.conversationMessage = page.getByTestId('conversation-message')
}
async verifyMessagesPage() {
await expect(this.messagesPageHeader).toBeVisible()
}
async createNewMessage(username: string[]) {
await expect(this.newMessageButton).toBeVisible()
await this.newMessageButton.click()
await expect(this.newMessageSearchUsers).toBeVisible()
for (let i = 0; i < username.length; i++) {
await this.newMessageSearchUsers.fill(username[i])
await sleep(1000)
await expect(this.newMessageSearchResults).toBeVisible()
const results = await this.newMessageSearchResults
.getByTestId('search-results-username')
.all()
const targetUser = username[i].toLowerCase()
for (let j = 0; j < results.length; j++) {
const usernameResults = (await results[j].textContent())?.toLowerCase()
if (usernameResults === targetUser) {
await results[j].click()
break
}
}
}
await expect(this.newMessageSearchCreateButton).toBeVisible()
await this.newMessageSearchCreateButton.click()
}
async sendMessage(message: string) {
await expect(this.messageInput).toBeVisible()
await this.messageInput.fill(message)
await expect(this.messageSubmit).toBeVisible()
await this.messageSubmit.click()
const verified = await this.verifyMessage(message)
if (!verified)
throw new Error(`Message "${message}" was not found in conversation after sending`)
}
async findMessageConversation(displayName: string) {
await expect(this.messagesTable).toBeVisible()
await this.page.waitForTimeout(1000)
const doMessagesExist = (await this.messagesRow.count()) > 0
if (doMessagesExist) {
const messages = await this.messagesRow.getByTestId('messages-username').all()
for (let i = 0; i < messages.length; i++) {
await expect(messages[i]).toBeVisible()
const messageFromUser = await messages[i].textContent()
if (messageFromUser?.toLowerCase() === displayName.toLowerCase()) await messages[i].click()
}
} else {
throw new Error('There are no messages on this account')
}
}
async verifyMessage(messageContent: string) {
await expect(this.conversation).toBeVisible()
await sleep(1000)
const messageCount = (await this.conversationMessage.count()) > 0
if (messageCount) {
const messages = await this.conversationMessage.all()
for (let i = 0; i < messages.length; i++) {
const message = await messages[i].textContent()
if (message?.toLowerCase() === messageContent.toLowerCase()) return true
}
return false
} else {
throw new Error('There are no messages in this conversation')
}
}
}

View File

@@ -1,7 +1,6 @@
import {expect, Locator, Page} from '@playwright/test'
import {
ConnectionTypeTuple,
GenderTuple,
EducationTuple,
DietTuple,
PsychedelicsTuple,
@@ -11,6 +10,7 @@ import {
ReligionTuple,
PersonalityKey,
LastActiveTuple,
InterestedInGenderTuple,
} from 'common/choices'
import {MinMaxNumbers} from '../utils/accountInformation'
@@ -25,7 +25,7 @@ export type LifestyleFilter = {
cause?: string
diet?: DietTuple
alcohol?: MinMaxNumbers
smoker?: string
smoker?: 'Yes' | 'No' | 'Either'
psychedelics?: PsychedelicsTuple
cannabis?: CannabisTuple
language?: LanguageTuple
@@ -56,13 +56,30 @@ export type AdvancedFilter = {
export type DisplayFilter = {
cardSize?: 'Small' | 'Medium' | 'Large'
filters?: [string, boolean][]
filters?: {
Gender?: boolean
City?: boolean
Age?: boolean
Headline?: boolean
Keywords?: boolean
'What they seek'?: boolean
Work?: boolean
Interests?: boolean
Causes?: boolean
Diet?: boolean
Smoking?: boolean
Drinks?: boolean
MBTI?: boolean
Languages?: boolean
Bio?: boolean
'Profile photo'?: boolean
}
}
export type PeoplePageFilter = {
connectionFilter?: ConnectionTypeTuple
ageFilter?: MinMaxNumbers
genderFilter?: GenderTuple
genderFilter?: InterestedInGenderTuple
backgroundFilter?: BackgroundFilter
lifestyleFilter?: LifestyleFilter
valuesAndBeliefsFilter?: BeliefsFilter
@@ -71,7 +88,10 @@ export type PeoplePageFilter = {
export class PeoplePage {
private readonly peopleHeading: Locator
private readonly savedPeopleHeading: Locator
private readonly savedPeopleList: Locator
private readonly searchBox: Locator
private readonly savedPeopleButton: Locator
private readonly profileCount: Locator
private readonly resetFilters: Locator
private readonly yourFiltersCheckbox: Locator
@@ -105,12 +125,20 @@ export class PeoplePage {
private readonly displayDropdown: Locator
private readonly profileGrid: Locator
private readonly profileResults: Locator
private readonly profileHide: Locator
private readonly profileStar: Locator
private readonly profileMessage: Locator
private readonly messageInput: Locator
private readonly profileName: Locator
private readonly profileAgeGender: Locator
private readonly profileSeeking: Locator
constructor(public readonly page: Page) {
this.peopleHeading = page.getByRole('heading', {name: 'People'})
this.savedPeopleHeading = page.getByRole('heading', {name: 'Saved People'})
this.savedPeopleList = page.getByTestId('saved-person')
this.searchBox = page.getByRole('textbox', {name: 'Search anything...'})
this.savedPeopleButton = page.getByRole('button', {name: 'Saved People'})
this.profileCount = page.getByTestId('people-profile-count')
this.resetFilters = page.getByRole('button', {name: 'Reset filters'})
this.yourFiltersCheckbox = page.getByText('Your filters', {exact: true})
@@ -144,13 +172,27 @@ export class PeoplePage {
this.displayDropdown = page.getByRole('button', {name: 'Display'})
this.profileGrid = page.getByTestId('people-profile-grid')
this.profileResults = page.getByTestId('people-profile-results')
this.profileHide = page.getByTestId('hide-profile-button')
this.profileStar = page.getByTestId('star-profile-button')
this.profileMessage = page.getByTestId('message-profile-button')
this.messageInput = page.locator('.tiptap')
this.profileName = page.getByTestId('people-profile-name')
this.profileAgeGender = page.getByTestId('people-profile-age-gender')
this.profileSeeking = page.getByTestId('people-profile-seeking')
}
get profileCountLocator(): Locator {
return this.profileCount
}
get profileNameLocator(): Locator {
return this.profileName
}
get profileAgeGenderLocator(): Locator {
return this.profileAgeGender
}
get profileSeekingLocator(): Locator {
return this.profileSeeking
}
async sliderHelper(range: MinMaxNumbers, locator?: Locator) {
let minSlider
@@ -219,12 +261,16 @@ export class PeoplePage {
await expect(this.peopleHeading).toBeVisible()
}
//Doesn't actually work, need to find out why
async clickSavedPeopleButton() {
await expect(this.savedPeopleButton).toBeVisible()
await this.savedPeopleButton.click()
}
async useSearch(item: string) {
await expect(this.searchBox).toBeVisible()
await this.searchBox.click()
await this.searchBox.fill(item)
await this.page.keyboard.press('Enter')
await this.page.waitForTimeout(1000)
}
async resetFilter() {
@@ -263,7 +309,7 @@ export class PeoplePage {
await this.sliderHelper(ageRange)
}
async setGenderTypeFilter(genderType: GenderTuple) {
async setGenderTypeFilter(genderType: InterestedInGenderTuple) {
await this.selectOption(this.genderDropdown, genderType[0])
// await expect(this.genderDropdown).toBeVisible()
// await this.genderDropdown.click()
@@ -395,19 +441,14 @@ export class PeoplePage {
if (display.cardSize) await this.page.getByRole('button', {name: `${display.cardSize}`}).click()
if (!display.filters) return
if (display.filters?.length > 0) {
for (let i = 0; i < display.filters.length; i++) {
const filter = await this.page.getByRole('checkbox', {name: `${display.filters[i][0]}`})
if (display.filters) {
for (const [name, shouldBeChecked] of Object.entries(display.filters)) {
const filter = await this.page.getByRole('checkbox', {name, exact: true})
await expect(filter).toBeVisible()
const isChecked = await filter.isChecked()
if (display.filters[i][1]) {
if (isChecked) continue
if (!isChecked) await filter.click()
} else if (!display.filters[i][1]) {
if (isChecked) await filter.click()
if (!isChecked) continue
}
if (shouldBeChecked && !isChecked) await filter.click()
if (!shouldBeChecked && isChecked) await filter.click()
}
}
}
@@ -415,20 +456,77 @@ export class PeoplePage {
async getProfileInfo() {
await expect(this.profileGrid).toBeVisible()
const totalResults = await this.profileResults.count()
if (totalResults === 0) throw Error('No profiles found')
const chosenProfileNumber = Math.floor(Math.random() * totalResults)
const chosenProfile = await this.profileResults.nth(chosenProfileNumber)
const profileName = await chosenProfile.getByTestId('people-profile-name').textContent()
const ageGender = await chosenProfile.getByTestId('people-profile-age-gender').textContent()
const seekingInfo = await chosenProfile.getByTestId('people-profile-seeking').textContent()
const hideProfile = await chosenProfile.getByTestId('hide-profile-button')
const starProfile = await chosenProfile.getByTestId('star-profile-button')
const messageProfile = await chosenProfile.getByTestId('message-profile-button')
if (!profileName) return
return {
name: profileName,
profile: chosenProfile ?? '',
name: profileName ?? '',
ageGender: ageGender ?? '',
seeking: seekingInfo ?? '',
hide: hideProfile ?? '',
star: starProfile ?? '',
message: messageProfile ?? '',
}
}
async verifyNumberOfMatchingProfiles(count: number) {
await expect(this.profileCount).toBeVisible()
const actualCount = await this.profileCount.textContent()
if (!actualCount) return
expect(parseInt(actualCount)).toStrictEqual(count)
async verifyProfileCount(totalProfiles: string) {
const exists = (await this.profileCountLocator.count()) > 0
if (exists) {
const filterdProfiles = await this.profileCountLocator.textContent()
if (!totalProfiles || !filterdProfiles) return
await expect(parseInt(totalProfiles)).not.toEqual(parseInt(filterdProfiles))
} else {
const noProfilesFound = await this.page.getByText('No profiles found.', {exact: true})
await expect(noProfilesFound).toBeVisible()
}
}
async selectProfile(displayName: string) {
await expect(this.profileGrid).toBeVisible()
await this.profileName.getByText(displayName).click()
}
async messageProfile(displayName: string, message: string) {
await expect(this.profileGrid).toBeVisible()
const profiles = await this.profileResults.all()
for (let i = 0; i < profiles.length; i++) {
const profileName = await profiles[i].getByTestId('people-profile-name').textContent()
if (profileName?.toLowerCase() === displayName.toLowerCase()) {
await profiles[i].getByTestId('message-profile-button').click()
await expect(this.messageInput).toBeVisible()
await this.messageInput.fill(message)
await this.page.getByTestId('conversation-message-submit').click()
return
}
}
}
async verifySavedPerson(displayName: string) {
await expect(this.savedPeopleHeading).toBeVisible()
await this.page.waitForTimeout(1000)
const isThereSavedPeople = (await this.savedPeopleList.count()) > 0
if (isThereSavedPeople) {
const listOfPeople = await this.savedPeopleList.all()
for (let i = 0; i < listOfPeople.length; i++) {
await expect(listOfPeople[i]).toBeVisible()
const profileName = await listOfPeople[i].textContent()
if (profileName?.toLowerCase() === displayName.toLowerCase()) return true
}
return false
} else {
throw new Error('There are no profiles in the saved people list')
}
}
}

View File

@@ -504,7 +504,6 @@ export class ProfilePage {
async verifyKeywords(keywords: string | undefined) {
if (!keywords) return
console.log(this.keywordsSection.textContent())
const keywordsArr = keywords.split(', ')
await expect(this.keywordsSection).toBeVisible()
for (const word of keywordsArr) {

View File

@@ -1,15 +1,27 @@
import axios from 'axios'
import {config} from '../SPEC_CONFIG'
import {
firebaseLoginEmailPassword,
getOobCode,
getUserId,
sendVerificationEmail,
} from '../../utils/firebaseUtils'
async function setup() {
const results = await axios.post(`${config.FIREBASE_URL.BASE}${config.FIREBASE_URL.SIGNUP}`, {
email: 'trial_test@email.com',
password: 'trialTestPassword',
returnSecureToken: true,
})
const loginInfo = await firebaseLoginEmailPassword('AnotherTest@email.com', 'Password')
await sendVerificationEmail(loginInfo.data.idToken)
const oobResponse = await axios.get(`${config.FIREBASE_URL.FIREBASE_EMULATOR_API}`)
const oobCode = await getOobCode(oobResponse.data.oobCodes, 'AnotherTest@email.com')
console.log(oobCode)
console.log('Auth created: ', 'trial_test@email.com')
console.log('Id: ', results.data.localId)
const response = await axios.post(
`${config.FIREBASE_URL.BASE}${config.FIREBASE_URL.CONFIRM_EMAIL_VERIFICATION}`,
{
oobCode,
},
)
console.log(response)
console.log(response.status)
}
setup()

View File

@@ -1,3 +1,6 @@
import {sleep} from 'common/util/time'
import {TEST_USER_DISPLAY_NAME} from '../../utils/seedDatabase'
import {expect, test} from '../fixtures/signInFixture'
test.describe('when given valid input', () => {
@@ -10,27 +13,277 @@ test.describe('when given valid input', () => {
await app.home.verifySignedInHomePage(account.display_name)
})
test('the profile count should update successfully when applying a filter', async ({
app,
signedOutAccount: account,
}) => {
test('should be able to save/favorite people', async ({app, signedOutAccount: account}) => {
await app.signinWithEmail(account)
await app.home.clickPeopleLink()
await app.people.getProfileInfo()
const profile = await app.people.getProfileInfo()
await expect(profile.star).toBeVisible()
await profile.star.click()
await sleep(1000)
await app.people.clickSavedPeopleButton()
await app.people.verifySavedPerson(profile.name)
})
const totalProfiles = await app.people.profileCountLocator.textContent()
expect(totalProfiles).toBeTruthy()
const totalCount = Number.parseInt(totalProfiles!, 10)
expect(Number.isNaN(totalCount)).toBe(false)
test.describe('the applied filter should', () => {
test('update the profile count', async ({app, signedOutAccount: account}) => {
await app.signinWithEmail(account)
await app.home.clickPeopleLink()
await app.people.setConnectionTypeFilter(['Collaboration', 'collaboration'])
const totalProfiles = await app.people.profileCountLocator.textContent()
await app.people.setConnectionTypeFilter(['Collaboration', 'collaboration'])
await app.people.setDisplayFilter({cardSize: 'Large'})
const filteredProfiles = await app.people.profileCountLocator.textContent()
// The count updates asynchronously after the filter is applied, so poll until it changes.
await expect
.poll(async () =>
Number.parseInt((await app.people.profileCountLocator.textContent())!, 10),
await expect(totalProfiles).not.toBeNull()
await expect(filteredProfiles).not.toBeNull()
console.log(totalProfiles)
console.log(filteredProfiles)
await expect(Number(totalProfiles?.split(' ')[0])).not.toEqual(
Number(filteredProfiles?.split(' ')[0]),
)
.not.toEqual(totalCount)
const results = await app.people.getProfileInfo()
if (!results) return
await expect(results.seeking).toContain('Collaboration')
})
/**
* Test fails due to ui not updating
* works fine manually
*/
test.skip('show profiles with the correct age', async ({app, signedOutAccount: account}) => {
await app.signinWithEmail(account)
await app.home.clickPeopleLink()
await app.people.setDisplayFilter({filters: {Age: true}})
const totalProfiles = await app.people.profileCountLocator.textContent()
const profileResults = await app.people.getProfileInfo()
const profileAge = parseInt(profileResults.ageGender.match(/\d+/)?.[0] ?? '0')
const age = profileAge <= 60 ? profileAge : 60
console.log(profileResults, age)
await app.people.setAgeRangeFilter({min: String(age), max: String(age)})
const filterdProfiles = await app.people.profileCountLocator.textContent()
if (!totalProfiles || !filterdProfiles) return
await expect(parseInt(totalProfiles)).not.toEqual(parseInt(filterdProfiles))
})
test('show profiles with the correct gender', async ({app, signedOutAccount: account}) => {
await app.signinWithEmail(account)
await app.home.clickPeopleLink()
const totalProfiles = await app.people.profileCountLocator.textContent()
await app.people.setGenderTypeFilter(['Woman', 'female'])
await app.people.setDisplayFilter({cardSize: 'Large'})
if (!totalProfiles) return
await app.people.verifyProfileCount(totalProfiles)
})
test('show profiles with the correct education level', async ({
app,
signedOutAccount: account,
}) => {
await app.signinWithEmail(account)
await app.home.clickPeopleLink()
const totalProfiles = await app.people.profileCountLocator.textContent()
await app.people.setBackgroundFilter({education: ['College', 'some-college']})
await app.people.setDisplayFilter({cardSize: 'Large'})
if (!totalProfiles) return
await app.people.verifyProfileCount(totalProfiles)
})
test('show profiles with the correct diet', async ({app, signedOutAccount: account}) => {
await app.signinWithEmail(account)
await app.home.clickPeopleLink()
const totalProfiles = await app.people.profileCountLocator.textContent()
await app.people.setLifestyleFilter({diet: ['Vegetarian', 'veg']})
await app.people.setDisplayFilter({cardSize: 'Large'})
if (!totalProfiles) return
await app.people.verifyProfileCount(totalProfiles)
})
test('show profiles with the correct smoking preference', async ({
app,
signedOutAccount: account,
}) => {
await app.signinWithEmail(account)
await app.home.clickPeopleLink()
const totalProfiles = await app.people.profileCountLocator.textContent()
await app.people.setLifestyleFilter({smoker: 'Yes'})
await app.people.setDisplayFilter({cardSize: 'Large'})
if (!totalProfiles) return
await app.people.verifyProfileCount(totalProfiles)
})
test('show profiles with the correct psychedelics preference', async ({
app,
signedOutAccount: account,
}) => {
await app.signinWithEmail(account)
await app.home.clickPeopleLink()
const totalProfiles = await app.people.profileCountLocator.textContent()
await app.people.setLifestyleFilter({psychedelics: ['Regularly (weekly+)', 'regularly']})
await app.people.setDisplayFilter({cardSize: 'Large'})
if (!totalProfiles) return
await app.people.verifyProfileCount(totalProfiles)
})
test('show profiles with the correct cannabis preference', async ({
app,
signedOutAccount: account,
}) => {
await app.signinWithEmail(account)
await app.home.clickPeopleLink()
const totalProfiles = await app.people.profileCountLocator.textContent()
await app.people.setLifestyleFilter({
cannabis: ['Occasionally (a few times a year)', 'occasionally'],
})
await app.people.setDisplayFilter({cardSize: 'Large'})
if (!totalProfiles) return
await app.people.verifyProfileCount(totalProfiles)
})
test('show profiles with the correct political preference', async ({
app,
signedOutAccount: account,
}) => {
await app.signinWithEmail(account)
await app.home.clickPeopleLink()
const totalProfiles = await app.people.profileCountLocator.textContent()
await app.people.setValuesAndBeliefsFilter({political: ['Progressive', 'progressive']})
await app.people.setDisplayFilter({cardSize: 'Large'})
if (!totalProfiles) return
await app.people.verifyProfileCount(totalProfiles)
})
test('show profiles with the correct religion preference', async ({
app,
signedOutAccount: account,
}) => {
await app.signinWithEmail(account)
await app.home.clickPeopleLink()
const totalProfiles = await app.people.profileCountLocator.textContent()
await app.people.setValuesAndBeliefsFilter({religious: ['Jewish', 'jewish']})
await app.people.setDisplayFilter({cardSize: 'Large'})
if (!totalProfiles) return
await app.people.verifyProfileCount(totalProfiles)
})
})
test.describe('the hide profile feature', () => {
test('should correctly hide a profile', async ({app, signedOutAccount: account}) => {
await app.signinWithEmail(account)
await app.home.clickPeopleLink()
await app.people.useSearch(TEST_USER_DISPLAY_NAME)
await sleep(1000)
const results = await app.people.getProfileInfo()
console.log(results)
const hideProfileButton = await results.profile.getByRole('button', {
name: 'Hide this profile',
})
await expect(hideProfileButton).toBeVisible()
await hideProfileButton.click()
await expect(
app.page.getByText(`You won't see ${results.name} in your search results anymore.`),
).toBeVisible()
})
test('should be reversible using undo', async ({app, signedOutAccount: account}) => {
await app.signinWithEmail(account)
await app.home.clickPeopleLink()
const results = await app.people.getProfileInfo()
if (!results) return
const hideProfileButton = await results.profile.getByRole('button', {
name: 'Hide this profile',
})
await expect(hideProfileButton).toBeVisible()
await hideProfileButton.click()
const hideProfileMessage = await app.page.getByText(
`You won't see ${results.name} in your search results anymore.`,
)
await expect(hideProfileMessage).toBeVisible()
await app.people.page.getByRole('button', {name: 'Undo'}).click()
await expect(hideProfileMessage).not.toBeVisible()
const profile = await app.people.page.getByRole('heading', {name: `${results.name}`})
await expect(profile).toBeVisible()
})
test('should be reversible using manage hidden profiles feature in settings', async ({
app,
signedOutAccount: account,
}) => {
await app.signinWithEmail(account)
await app.home.clickPeopleLink()
const results = await app.people.getProfileInfo()
if (!results) return
const hideProfileButton = await results.profile.getByRole('button', {
name: 'Hide this profile',
})
await expect(hideProfileButton).toBeVisible()
await hideProfileButton.click()
const hideProfileMessage = await app.page.getByText(
`You won't see ${results.name} in your search results anymore.`,
)
await expect(hideProfileMessage).toBeVisible()
await app.home.clickSettingsLink()
await app.settings.clickManageHiddenProfilesButton()
await app.settings.verifyHiddenProfiles([results.name])
await app.settings.unhideProfiles(results.name)
await app.settings.clickCloseButton()
await app.home.clickPeopleLink()
const profile = await app.people.page.getByRole('heading', {name: `${results.name}`})
await expect(profile).toBeVisible()
})
})
test.describe('a verified account should', () => {
const message = 'This is a message'
test('be able to send a message from the messages page', async ({
app,
signedInAccount: sender,
signedOutAccount: receiver,
}) => {
const receiverApp = await app.contextManager.createContext()
await receiverApp.signinWithEmail(receiver)
await app.home.clickMessagesLink()
await app.messages.createNewMessage([receiver.display_name])
await app.messages.sendMessage(message)
await receiverApp.home.clickMessagesLink()
await receiverApp.messages.findMessageConversation(sender.display_name)
await receiverApp.messages.verifyMessage(message)
})
test('be able to send a message from the people page', async ({
app,
signedInAccount: sender,
signedOutAccount: receiver,
}) => {
const receiverApp = await app.contextManager.createContext()
await receiverApp.signinWithEmail(receiver)
// To pass the min character limit for message intro (250 chars)
const longMessage = message.repeat(20)
await app.home.clickPeopleLink()
await app.people.useSearch(receiver.display_name)
await app.people.messageProfile(receiver.display_name, longMessage)
await app.messages.verifyMessage(longMessage)
await receiverApp.home.clickMessagesLink()
await receiverApp.messages.findMessageConversation(sender.display_name)
await receiverApp.messages.verifyMessage(longMessage)
})
})
})

View File

@@ -28,4 +28,4 @@ test.describe('when an error occurs', () => {
await app.signUp.verifyUsernameError()
await expect(app.signUp.nextButtonLocator).toBeDisabled()
})
})
})

View File

@@ -130,6 +130,7 @@ export function ChatMessageItem(props: {
onTouchStart={() => startLongPress(chat.id)}
onTouchEnd={cancelLongPress}
onTouchCancel={cancelLongPress}
data-testid="conversation-message"
>
<Content
size={'sm'}

View File

@@ -227,6 +227,7 @@ export function CommentInputTextArea(props: {
)}
{!isSubmitting && !isDisabled && submit && commentTypes.includes('comment') && (
<button
data-testid="conversation-message-submit"
className="text-ink-500 hover:text-ink-700 active:bg-ink-300 disabled:text-ink-300 px-4 transition-colors"
disabled={isDisabled || !editor || editor.isEmpty}
onClick={() => submit('comment')}

View File

@@ -232,7 +232,10 @@ function Filters(props: {
const [noMinAge, noMaxAge] = getNoMinMaxAge(filters.pref_age_min, filters.pref_age_max)
return (
<Col className="mb-[calc(var(--filter-offset)+env(safe-area-inset-bottom))] mt-[calc(var(--filter-offset)+env(safe-area-inset-top))] pt-3 pb-1">
<Col
className="mb-[calc(var(--filter-offset)+env(safe-area-inset-bottom))] mt-[calc(var(--filter-offset)+env(safe-area-inset-top))] pt-3 pb-1"
data-testid="people-filters"
>
<SelectedFiltersSummary
filters={filters}
locationFilterProps={locationFilterProps}

View File

@@ -1,10 +1,19 @@
import {ReactNode} from 'react'
import {Row} from 'web/components/layout/row'
export function IconWithInfo(props: {text?: string; icon: ReactNode; children?: ReactNode}) {
const {text, icon, children} = props
export function IconWithInfo(props: {
text?: string
icon: ReactNode
children?: ReactNode
testid?: string
}) {
const {text, icon, children, testid} = props
return (
<Row className="items-center gap-1" style={{gap: '5px'}}>
<Row
className="items-center gap-1"
style={{gap: '5px'}}
{...(testid ? {'data-testid': testid} : {})}
>
<div className="mt-0.5" style={{width: '14px', height: '14px'}}>
{icon}
</div>

View File

@@ -1005,7 +1005,7 @@ export const OptionalProfileUserForm = (props: {
{t('profile.optional.mbti', 'MBTI Personality Type')}
</label>
<ChoicesToggleGroup
currentChoice={profile['mbti'] ?? ''}
currentChoice={profile['mbti'] as any}
choicesMap={MBTI_CHOICES}
setChoice={(c) => setProfile('mbti', c)}
className="grid grid-cols-4 xs:grid-cols-8"

View File

@@ -431,7 +431,10 @@ function ProfilePreview(props: {
/>
)}
{user && (
<div className={clsx(cardSize !== 'large' && 'hidden sm:flex')}>
<div
className={clsx(cardSize !== 'large' && 'hidden sm:flex')}
data-testid="message-profile-button"
>
<SendMessageButton
toUser={user}
profile={profile}
@@ -506,6 +509,7 @@ function ProfilePreview(props: {
)}
{showSeeking !== false && seekingText && (
<IconWithInfo
testid="people-profile-seeking"
text={seekingText}
icon={<PiMagnifyingGlassBold className="h-4 w-4 " />}
/>

View File

@@ -230,7 +230,7 @@ function StarModal(props: {
</p>
<Col className={clsx('divide-y divide-canvas-200 w-full pr-2', SCROLLABLE_MODAL_CLASS)}>
{visibleUsers.map((u) => (
<div key={u.id} className="py-3 first:pt-0 last:pb-0">
<div key={u.id} className="py-3 first:pt-0 last:pb-0" data-testid="saved-person">
<div className="bg-canvas-0 border border-canvas-200 rounded-xl p-3 transition-all hover:border-primary-300 hover:shadow-sm">
<Row className="items-center justify-between gap-3">
<Link className="flex-1 group" href={'/' + u.username}>
@@ -243,7 +243,10 @@ function StarModal(props: {
/>
</div>
<Col className="flex-1">
<div className="font-medium text-ink-900 group-hover:text-primary-600 transition-colors">
<div
className="font-medium text-ink-900 group-hover:text-primary-600 transition-colors"
data-testid="saved-person-display-name"
>
{u.name}
</div>
<div className="text-ink-500 text-sm">@{u.username}</div>
@@ -251,6 +254,7 @@ function StarModal(props: {
</Row>
</Link>
<button
data-testid="remove-saved-person"
onClick={() => {
// Optimistically remove the user from the list
setRemovingIds((prev) => new Set(prev).add(u.id))

View File

@@ -94,6 +94,7 @@ export function SelectUsers(props: {
'relative inline-block w-full overflow-y-scroll text-right',
queryReady && 'h-56',
)}
data-testid="search-results"
>
{() => (
<Transition
@@ -112,7 +113,7 @@ export function SelectUsers(props: {
>
<div className="py-1">
{filteredUsers.map((user) => (
<Menu.Item key={user.id}>
<Menu.Item key={user.id} data-testid="search-results-username">
{({active}) => (
<button
className={clsx(

View File

@@ -76,6 +76,7 @@ export function HideProfileButton(props: HideProfileButtonProps) {
noTap
>
<button
data-testid="hide-profile-button"
className={clsx(
'relative inline-flex items-center justify-center border bg-canvas-50 border-canvas-300 text-ink-300 hover:border-primary-400 hover:bg-primary-50 rounded-lg h-7 w-7',
className,

View File

@@ -43,6 +43,7 @@ export const StarButton = (props: {
const button = (
<button
data-testid="star-profile-button"
className={clsx(
'border border-canvas-200',
buttonClass('xs', 'none'),

View File

@@ -388,6 +388,7 @@ export const PrivateChat = (props: {
style={{
transform: isSafari ? 'translate3d(0, 0, 0)' : 'none',
}}
data-testid="conversation"
>
<div
className="relative px-1 pb-4 pt-1 transition-all duration-100"

View File

@@ -64,7 +64,7 @@ export function MessagesContent(props: {currentUser: User}) {
</h1>
<NewMessageButton />
</Row>
<Col className={'w-full overflow-hidden gap-2'}>
<Col className={'w-full overflow-hidden gap-2'} data-testid="messages-table">
{channels && channels.length === 0 && (
<div className="bg-canvas-50 border border-canvas-200 rounded-xl p-8 text-center mt-4">
<p className="text-ink-500 text-sm mb-1">
@@ -116,6 +116,7 @@ export const MessageChannelRow = (props: {
className={
'items-center gap-3 bg-canvas-50 border border-canvas-200 rounded-xl p-3 transition-all hover:border-primary-300 hover:shadow-sm hover:bg-canvas-100'
}
data-testid="messages-row"
>
{otherUsers && otherUsers.length > 0 ? (
<MultipleOrSingleAvatars
@@ -135,6 +136,7 @@ export const MessageChannelRow = (props: {
className={
'font-medium text-ink-900 text-sm group-hover:text-primary-600 transition-colors'
}
data-testid="messages-username"
>
{otherUsers && otherUsers.length > 0 ? (
<span>
@@ -162,7 +164,7 @@ export const MessageChannelRow = (props: {
)}
{isBanned && <BannedBadge />}
</span>
<span className={'text-ink-300 text-xs'}>
<span className={'text-ink-300 text-xs'} data-testid="messages-timestamp">
{lastMessage && <RelativeTimestamp time={lastMessage.createdTime} />}
</span>
</Row>