[Syncing] Small PR to lay foundations for Testing filtering and to sync with upstream (#51)

* Added Database checks to the onboarding flow

* Added compatibility page setup
Added more compatibility questions

* Finished up the onboarding flow suite
Added compatibility question tests and verifications
Updated tests to cover Keywords and Headline changes recently made
Updated tests to cover all of the big5 personality traits

* .

* Fix: Merge conflict

* .

* Fix: Added fix for None discriptive error issue #36
Updated signUp.spec.ts to use new fixture
Updated Account information variable names
Deleted "deleteUserFixture.ts" as it was incorporated into the "base.ts" file

* Linting and Prettier

* Minor cleaning

* Organizing helper func

* Added Google account to the Onboarding flow

* Added account cleanup for google accounts

* Started work on Sign-in tests
Updated seedDatabase.ts to throw an error if the user already exists, to also add display names and usernames so they seedUser func acts like a normal basic user
Some organising of the google auth code

* Linting and Prettier

* Added checks to the deleteUser func to check if the accout exists
Added account deletion checks

* Linting and Prettier

* Formatting update, fixed homePage locator for signin

* .

* .

* .

* Coderabbitai fix's

* Fix

* Improve test utilities and stabilize onboarding flow tests

* Changes requested

* Changed POM/Fixture structure to use an app class to instantiate the page objects

* Apply suggestion from @MartinBraquet

* Delete .vscode/settings.json

* Apply suggestion from @MartinBraquet

* Apply suggestion from @MartinBraquet

* Apply suggestion from @MartinBraquet

* Linting and Prettier

* Updated People page

* Fix app.ts

* Updated peoplePage.ts: continued adding functions to use filters
Updated filters.tsx: added data testid

* Coderabbitai fix's

* .

* Explanded seeding to better reflect the different types of profiles

* Updated People page
Added data test attributes to search.tsx and profile-grid.tsx

* Lint and Prettier

* Test renamed

* CodeRabbit Suggestions

* .

* Apply suggestions from code review

Co-authored-by: Martin Braquet <martin.braquet@gmail.com>

---------

Co-authored-by: MartinBraquet <martin.braquet@gmail.com>
This commit is contained in:
Okechi Jones-Williams
2026-05-20 19:02:00 +01:00
committed by GitHub
parent 9262e4a2be
commit 00c6f2566e
20 changed files with 583 additions and 41 deletions

View File

@@ -1,7 +1,7 @@
name: API Release
on:
push:
branches: [ main, master ]
branches: [main, master]
paths:
- 'backend/api/package.json'
- '.github/workflows/cd-api.yml'

View File

@@ -2,9 +2,9 @@ name: CI
on:
push:
branches: [ main ]
branches: [main]
pull_request:
branches: [ main ]
branches: [main]
jobs:
lint:

View File

@@ -1,16 +1,7 @@
import {
Body,
Button,
Container,
Head,
Html,
Preview,
Section,
Text,
} from '@react-email/components'
import {Body, Button, Container, Head, Html, Preview, Section, Text} from '@react-email/components'
import {DOMAIN} from 'common/envs/constants'
import {type User} from 'common/user'
import { container, content, Footer, main} from 'email/utils'
import {container, content, Footer, main} from 'email/utils'
import React from 'react'
import {createT} from 'shared/locale'

View File

@@ -13,7 +13,7 @@ import {ANDROID_APP_URL} from 'common/constants'
import {DOMAIN} from 'common/envs/constants'
import {type ProfileRow} from 'common/profiles/profile'
import {type User} from 'common/user'
import { container, content, Footer, main} from 'email/utils'
import {container, content, Footer, main} from 'email/utils'
import React from 'react'
import {createT} from 'shared/locale'

View File

@@ -339,3 +339,7 @@ export type SubstanceIntentionTuple = {
export type SubstancePreferenceTuple = {
[K in keyof typeof SUBSTANCE_PREFERENCE_CHOICES]: [K, (typeof SUBSTANCE_PREFERENCE_CHOICES)[K]]
}[keyof typeof SUBSTANCE_PREFERENCE_CHOICES]
export type LastActiveTuple = {
[K in keyof typeof LAST_ONLINE_CHOICES]: [K, (typeof LAST_ONLINE_CHOICES)[K]]
}[keyof typeof LAST_ONLINE_CHOICES]

View File

@@ -7,9 +7,13 @@ import {
PSYCHEDELICS_CHOICES,
RACE_CHOICES,
RELATIONSHIP_CHOICES,
RELATIONSHIP_STATUS_CHOICES,
ROMANTIC_CHOICES,
RELIGION_CHOICES,
LANGUAGE_CHOICES,
SUBSTANCE_INTENTION_CHOICES,
SUBSTANCE_PREFERENCE_CHOICES,
MBTI_CHOICES,
} from 'common/choices'
class UserAccountInformationForSeeding {
@@ -30,8 +34,13 @@ class UserAccountInformationForSeeding {
min: faker.number.int({min: 18, max: 27}),
max: faker.number.int({min: 36, max: 68}),
}
has_kids = faker.number.int({min: 0, max: 5})
wants_kids_strength = faker.number.int({min: 0, max: 4})
is_smoker = faker.datatype.boolean()
relationship_status = Object.values(RELATIONSHIP_STATUS_CHOICES)
pref_relation_styles = Object.values(RELATIONSHIP_CHOICES)
pref_romantic_styles = Object.values(ROMANTIC_CHOICES)
languages = Object.values(LANGUAGE_CHOICES)
political_beliefs = Object.values(POLITICAL_CHOICES)
religion = Object.values(RELIGION_CHOICES)
diet = Object.values(DIET_CHOICES)
@@ -42,6 +51,7 @@ class UserAccountInformationForSeeding {
company = faker.company.name()
occupation_title = faker.person.jobTitle()
university = faker.company.name()
keywords = faker.lorem.word()
cannabis = Object.values(CANNABIS_CHOICES)
psychedelics = Object.values(PSYCHEDELICS_CHOICES)
@@ -49,6 +59,12 @@ class UserAccountInformationForSeeding {
cannabis_pref = Object.values(SUBSTANCE_PREFERENCE_CHOICES)
psychedelics_intention = Object.values(SUBSTANCE_INTENTION_CHOICES)
psychedelics_pref = Object.values(SUBSTANCE_PREFERENCE_CHOICES)
mbti = Object.values(MBTI_CHOICES)
big5_openness = faker.number.int({min: 0, max: 100})
big5_conscientiousness = faker.number.int({min: 0, max: 100})
big5_extraversion = faker.number.int({min: 0, max: 100})
big5_agreeableness = faker.number.int({min: 0, max: 100})
big5_neuroticism = faker.number.int({min: 0, max: 100})
randomElement(array: Array<string>) {
return array[Math.floor(Math.random() * array.length)].toLowerCase()

View File

@@ -78,7 +78,10 @@ export async function deleteAccount(idToken: any) {
* Check if a Firebase user exists by email
* Returns userId if exists, undefined if not found
*/
export async function firebaseUserExists(email: string, password: string): Promise<string | undefined> {
export async function firebaseUserExists(
email: string,
password: string,
): Promise<string | undefined> {
try {
const login = await firebaseLoginEmailPassword(email, password)
return login.data.localId

View File

@@ -1,3 +1,4 @@
import {faker} from '@faker-js/faker'
import {debug} from 'common/logger'
import {PrivateUser} from 'common/user'
import {getDefaultNotificationPreferences} from 'common/user-notification-preferences'
@@ -37,13 +38,46 @@ export async function seedDbUser(
},
],
}
const relationshipStyle = userInfo.randomElement(userInfo.pref_relation_styles)
let romanticStyle: string | null = null
if (relationshipStyle === 'relationship') {
romanticStyle = userInfo.randomElement(userInfo.pref_romantic_styles)
}
const numberOfLanguages = faker.number.int({min: 1, max: 3})
let languagesKnown = []
for (let i = 0; i < numberOfLanguages; i++) {
languagesKnown.push(userInfo.randomElement(userInfo.languages))
}
const keywords = faker.number.int({min: 1, max: 4})
let profileKeywords = []
for (let i = 0; i < keywords; i++) {
profileKeywords.push(userInfo.keywords)
}
const basicProfile = {
user_id: userId,
bio_length: userInfo.bio.length,
bio: bio,
age: userInfo.age,
gender: userInfo.randomElement(userInfo.gender),
ethnicity: [userInfo.randomElement(userInfo.ethnicity)],
height_in_inches: userInfo.height_in_inches,
pref_gender: [userInfo.randomElement(userInfo.pref_gender)],
pref_relation_styles: [relationshipStyle],
relationship_status: [userInfo.randomElement(userInfo.relationship_status)],
pref_romantic_styles: romanticStyle ? [romanticStyle] : [],
pref_age_min: userInfo.pref_age.min,
pref_age_max: userInfo.pref_age.max,
born_in_location: userInfo.born_in_location,
company: userInfo.company,
occupation_title: userInfo.occupation_title,
religion: [userInfo.randomElement(userInfo.religion)],
has_kids: userInfo.has_kids,
wants_kids_strength: userInfo.wants_kids_strength,
is_smoker: userInfo.is_smoker,
}
const mediumProfile = {
@@ -51,17 +85,12 @@ export async function seedDbUser(
drinks_per_month: userInfo.drinks_per_month,
diet: [userInfo.randomElement(userInfo.diet)],
education_level: userInfo.randomElement(userInfo.education_level),
ethnicity: [userInfo.randomElement(userInfo.ethnicity)],
gender: userInfo.randomElement(userInfo.gender),
height_in_inches: userInfo.height_in_inches,
pref_gender: [userInfo.randomElement(userInfo.pref_gender)],
pref_age_min: userInfo.pref_age.min,
pref_age_max: userInfo.pref_age.max,
languages: languagesKnown,
keywords: profileKeywords,
}
const fullProfile = {
...mediumProfile,
occupation_title: userInfo.occupation_title,
cannabis: userInfo.randomElement(userInfo.cannabis),
psychedelics: userInfo.randomElement(userInfo.psychedelics),
cannabis_intention: [userInfo.randomElement(userInfo.cannabis_intention)],
@@ -69,8 +98,12 @@ export async function seedDbUser(
cannabis_pref: [userInfo.randomElement(userInfo.cannabis_pref)],
psychedelics_pref: [userInfo.randomElement(userInfo.psychedelics_pref)],
political_beliefs: [userInfo.randomElement(userInfo.political_beliefs)],
pref_relation_styles: [userInfo.randomElement(userInfo.pref_relation_styles)],
religion: [userInfo.randomElement(userInfo.religion)],
mbti: userInfo.randomElement(userInfo.mbti),
big5_openness: userInfo.big5_openness,
big5_conscientiousness: userInfo.big5_conscientiousness,
big5_extraversion: userInfo.big5_extraversion,
big5_agreeableness: userInfo.big5_agreeableness,
big5_neuroticism: userInfo.big5_neuroticism,
}
const profileData =

View File

@@ -10,6 +10,8 @@ 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
@@ -21,6 +23,8 @@ export class App {
readonly settings: SettingsPage
readonly signUp: SignUpPage
readonly social: SocialPage
readonly people: PeoplePage
readonly notifs: NotificationPage
constructor(public readonly page: Page) {
this.auth = new AuthPage(page)
@@ -32,6 +36,8 @@ export class App {
this.settings = new SettingsPage(page)
this.signUp = new SignUpPage(page)
this.social = new SocialPage(page)
this.people = new PeoplePage(page)
this.notifs = new NotificationPage(page)
}
async deleteProfileFromSettings() {

View File

@@ -55,8 +55,10 @@ export class AuthPage {
await popup.getByLabel('Email').fill(email)
if (display_name) await popup.getByLabel('Display name').fill(display_name)
if (username) await popup.getByLabel('Screen name', {exact: true}).fill(username)
await popup.getByText('Sign in with Google.com', {exact: true}).click()
await popup.waitForEvent('close')
await Promise.all([
popup.waitForEvent('close'),
popup.getByText('Sign in with Google.com', {exact: true}).click(),
])
}
async clickSignUpWithEmailButton() {

View File

@@ -0,0 +1,11 @@
import {expect, Locator, Page} from '@playwright/test'
export class NotificationPage {
private readonly notificationTab: Locator
private readonly settingsTab: Locator
constructor(public readonly page: Page) {
this.notificationTab = page.getByTestId('notifications-tab')
this.settingsTab = page.getByTestId('settings-tab')
}
}

View File

@@ -0,0 +1,434 @@
import {expect, Locator, Page} from '@playwright/test'
import {
ConnectionTypeTuple,
GenderTuple,
EducationTuple,
DietTuple,
PsychedelicsTuple,
CannabisTuple,
LanguageTuple,
PoliticalTuple,
ReligionTuple,
PersonalityKey,
LastActiveTuple,
} from 'common/choices'
import {MinMaxNumbers} from '../utils/accountInformation'
export type BackgroundFilter = {
location?: string
education?: EducationTuple
work?: string
}
export type LifestyleFilter = {
interest?: string
cause?: string
diet?: DietTuple
alcohol?: MinMaxNumbers
smoker?: string
psychedelics?: PsychedelicsTuple
cannabis?: CannabisTuple
language?: LanguageTuple
}
export type BeliefsFilter = {
political?: PoliticalTuple
religious?: ReligionTuple
}
export type PersonalityFilter = {
mbti?: PersonalityKey
bigFive?: BigFive
}
type BigFive = {
openness?: MinMaxNumbers
conscientiousness?: MinMaxNumbers
extraversion?: MinMaxNumbers
agreeableness?: MinMaxNumbers
neuroticism?: MinMaxNumbers
}
export type AdvancedFilter = {
lastActive?: LastActiveTuple
photos?: boolean
}
export type DisplayFilter = {
cardSize?: 'Small' | 'Medium' | 'Large'
filters?: [string, boolean][]
}
export type PeoplePageFilter = {
connectionFilter?: ConnectionTypeTuple
ageFilter?: MinMaxNumbers
genderFilter?: GenderTuple
backgroundFilter?: BackgroundFilter
lifestyleFilter?: LifestyleFilter
valuesAndBeliefsFilter?: BeliefsFilter
personalityFilter?: PersonalityFilter
}
export class PeoplePage {
private readonly peopleHeading: Locator
private readonly searchBox: Locator
private readonly profileCount: Locator
private readonly resetFilters: Locator
private readonly yourFiltersCheckbox: Locator
private readonly incompleteProfilesCheckbox: Locator
private readonly connectionTypeDropdown: Locator
private readonly locationDropdown: Locator
private readonly ageRangeDropdown: Locator
private readonly genderDropdown: Locator
private readonly backgroundDropdown: Locator
private readonly backgroundLocation: Locator
private readonly backgroundEducation: Locator
private readonly backgroundWork: Locator
private readonly lifestyleDropdown: Locator
private readonly lifestyleInterests: Locator
private readonly lifestyleCauses: Locator
private readonly lifestyleDiet: Locator
private readonly lifestyleAlcohol: Locator
private readonly lifestyleSmoker: Locator
private readonly lifestylePsychedelics: Locator
private readonly lifestyleCannabis: Locator
private readonly lifestyleLanguages: Locator
private readonly valuesAndBeliefsDropdown: Locator
private readonly valuesAndBeliefsPolitics: Locator
private readonly valuesAndBeliefsReligion: Locator
private readonly personalityDropdown: Locator
private readonly personalityMbti: Locator
private readonly personalityBigFive: Locator
private readonly advancedDropdown: Locator
private readonly advancedActive: Locator
private readonly advancedPhotos: Locator
private readonly displayDropdown: Locator
private readonly profileGrid: Locator
private readonly profileResults: Locator
private readonly profileName: Locator
private readonly profileAgeGender: Locator
constructor(public readonly page: Page) {
this.peopleHeading = page.getByRole('heading', {name: 'People'})
this.searchBox = page.getByRole('textbox', {name: 'Search anything...'})
this.profileCount = page.getByTestId('people-profile-count')
this.resetFilters = page.getByRole('button', {name: 'Reset filters'})
this.yourFiltersCheckbox = page.getByText('Your filters', {exact: true})
this.incompleteProfilesCheckbox = page.getByText('Include incomplete profiles', {exact: true})
this.connectionTypeDropdown = page.getByRole('button', {name: 'Any connection'})
this.locationDropdown = page.getByRole('button', {name: 'Living anywhere'})
this.ageRangeDropdown = page.getByRole('button', {name: 'Any age'})
this.genderDropdown = page.getByRole('button', {name: 'Any gender'})
this.backgroundDropdown = page.getByRole('button', {name: 'Background'})
this.backgroundLocation = page.getByRole('button', {name: 'Grew up anywhere'})
this.backgroundEducation = page.getByText('Any education', {exact: true})
this.backgroundWork = page.getByText('Any work', {exact: true})
this.lifestyleDropdown = page.getByRole('button', {name: 'Lifestyle'})
this.lifestyleInterests = page.getByRole('button', {name: 'Any interests'})
this.lifestyleCauses = page.getByRole('button', {name: 'Any causes'})
this.lifestyleDiet = page.getByRole('button', {name: 'Any diet'})
this.lifestyleAlcohol = page.getByRole('button', {name: 'Any drinks'})
this.lifestyleSmoker = page.getByTestId('lifestyle-smoker')
this.lifestylePsychedelics = page.getByRole('button', {name: 'Any psychedelics'})
this.lifestyleCannabis = page.getByRole('button', {name: 'Any cannabis'})
this.lifestyleLanguages = page.getByRole('button', {name: 'Any language'})
this.valuesAndBeliefsDropdown = page.getByRole('button', {name: 'Values & Beliefs'})
this.valuesAndBeliefsPolitics = page.getByRole('button', {name: 'Any politics'})
this.valuesAndBeliefsReligion = page.getByRole('button', {name: 'Any religion'})
this.personalityDropdown = page.getByRole('button', {name: 'Personality'})
this.personalityMbti = page.getByRole('button', {name: 'Any MBTI'})
this.personalityBigFive = page.getByRole('button', {name: 'Any Big 5'})
this.advancedDropdown = page.getByRole('button', {name: 'Advanced'})
this.advancedActive = page.getByTestId('advanced-active')
this.advancedPhotos = page.getByText('Photos', {exact: true})
this.displayDropdown = page.getByRole('button', {name: 'Display'})
this.profileGrid = page.getByTestId('people-profile-grid')
this.profileResults = page.getByTestId('people-profile-results')
this.profileName = page.getByTestId('people-profile-name')
this.profileAgeGender = page.getByTestId('people-profile-age-gender')
}
get profileCountLocator(): Locator {
return this.profileCount
}
async sliderHelper(range: MinMaxNumbers, locator?: Locator) {
let minSlider
let maxSlider
if (locator) {
minSlider = await locator.getByRole('slider', {name: 'Minimum'})
maxSlider = await locator.getByRole('slider', {name: 'Maximum'})
} else {
minSlider = await this.page.getByRole('slider', {name: 'Minimum'})
maxSlider = await this.page.getByRole('slider', {name: 'Maximum'})
}
await expect(minSlider).toBeVisible()
await expect(maxSlider).toBeVisible()
if (range.min === null || range.max === null) return
const minRange = Number(range.min)
const maxRange = Number(range.max)
const currentMinValue = Number(await minSlider.getAttribute('aria-valuenow'))
const currentMaxValue = Number(await maxSlider.getAttribute('aria-valuenow'))
if (isNaN(currentMinValue) || isNaN(currentMaxValue)) return
if (minRange > currentMinValue) {
await minSlider.click()
let iterations = 0
const MAX_ITERATIONS = 100
while (true) {
if (iterations++ > MAX_ITERATIONS) {
throw new Error(`Slider adjustment exceeded ${MAX_ITERATIONS} iterations`)
}
const changedMinValue = Number(await minSlider.getAttribute('aria-valuenow'))
if (isNaN(changedMinValue)) break
if (minRange <= changedMinValue) break
await this.page.keyboard.press('ArrowRight')
}
}
if (maxRange < currentMaxValue) {
await maxSlider.click()
let iterations = 0
const MAX_ITERATIONS = 100
while (true) {
if (iterations++ > MAX_ITERATIONS) {
throw new Error(`Slider adjustment exceeded ${MAX_ITERATIONS} iterations`)
}
const changedMaxValue = Number(await maxSlider.getAttribute('aria-valuenow'))
if (isNaN(changedMaxValue)) break
if (maxRange >= changedMaxValue) break
await this.page.keyboard.press('ArrowLeft')
}
}
}
async selectOption(trigger: Locator, label: string) {
await expect(trigger).toBeVisible()
await trigger.click()
const option = this.page.getByLabel(label, {exact: true})
await expect(option).toBeVisible()
await option.click()
}
async verifyPeoplePage() {
await expect(this.peopleHeading).toBeVisible()
}
//Doesn't actually work, need to find out why
async useSearch(item: string) {
await expect(this.searchBox).toBeVisible()
await this.searchBox.click()
await this.searchBox.fill(item)
await this.page.keyboard.press('Enter')
}
async resetFilter() {
await expect(this.resetFilters).toBeVisible()
await this.resetFilters.click()
}
async setYourFilters() {
await expect(this.yourFiltersCheckbox).toBeVisible()
await this.yourFiltersCheckbox.click()
}
async setIncludeIncompleteProfiles() {
await expect(this.incompleteProfilesCheckbox).toBeVisible()
await this.incompleteProfilesCheckbox.click()
}
async setConnectionTypeFilter(connectionType: ConnectionTypeTuple) {
await this.selectOption(this.connectionTypeDropdown, connectionType[0])
// await expect(this.connectionTypeDropdown).toBeVisible()
// await this.connectionTypeDropdown.click()
// await expect(this.page.getByLabel(connectionType[0])).toBeVisible()
// await this.page.getByLabel(connectionType[0]).click()
}
async setLocationFilter(location: string) {
await expect(this.locationDropdown).toBeVisible()
await this.locationDropdown.click()
await expect(this.page.getByRole('textbox', {name: 'Search city...'})).toBeVisible()
await this.page.getByRole('textbox', {name: 'Search city...'}).fill(location)
}
async setAgeRangeFilter(ageRange: MinMaxNumbers) {
await expect(this.ageRangeDropdown).toBeVisible()
await this.ageRangeDropdown.click()
await this.sliderHelper(ageRange)
}
async setGenderTypeFilter(genderType: GenderTuple) {
await this.selectOption(this.genderDropdown, genderType[0])
// await expect(this.genderDropdown).toBeVisible()
// await this.genderDropdown.click()
// await expect(this.page.getByLabel(genderType[0], {exact: true})).toBeVisible()
// await this.page.getByLabel(genderType[0], {exact: true}).click()
}
async setBackgroundFilter(background: BackgroundFilter) {
await expect(this.backgroundDropdown).toBeVisible()
await this.backgroundDropdown.click()
if (background.location) {
await expect(this.backgroundLocation).toBeVisible()
await this.backgroundLocation.click()
await this.page.getByPlaceholder('Search city...', {exact: true}).fill(background.location)
}
if (background.education) {
await expect(this.backgroundEducation).toBeVisible()
await this.backgroundEducation.click()
await expect(this.page.getByLabel(background.education[0], {exact: true})).toBeVisible()
await this.page.getByLabel(background.education[0], {exact: true}).click()
}
if (background.work) {
await expect(this.backgroundWork).toBeVisible()
await this.backgroundWork.click()
await expect(this.page.getByLabel(background.work, {exact: true})).toBeVisible()
await this.page.getByLabel(background.work, {exact: true}).click()
}
}
async setLifestyleFilter(lifestyle: LifestyleFilter) {
await expect(this.lifestyleDropdown).toBeVisible()
await this.lifestyleDropdown.click()
if (lifestyle.interest) await this.selectOption(this.lifestyleInterests, lifestyle.interest)
if (lifestyle.cause) await this.selectOption(this.lifestyleCauses, lifestyle.cause)
if (lifestyle.diet) await this.selectOption(this.lifestyleDiet, lifestyle.diet[0])
if (lifestyle.alcohol) {
await expect(this.lifestyleAlcohol).toBeVisible()
await this.lifestyleAlcohol.click()
await this.sliderHelper(lifestyle.alcohol)
}
if (lifestyle.smoker) {
await expect(this.lifestyleSmoker).toBeVisible()
await this.lifestyleSmoker.click()
await expect(this.page.getByText(lifestyle.smoker, {exact: true})).toBeVisible()
await this.page.getByText(lifestyle.smoker, {exact: true}).click()
}
if (lifestyle.psychedelics)
await this.selectOption(this.lifestylePsychedelics, lifestyle.psychedelics[0])
if (lifestyle.cannabis) await this.selectOption(this.lifestyleCannabis, lifestyle.cannabis[0])
if (lifestyle.language) await this.selectOption(this.lifestyleLanguages, lifestyle.language[0])
}
async setValuesAndBeliefsFilter(values: BeliefsFilter) {
await expect(this.valuesAndBeliefsDropdown).toBeVisible()
await this.valuesAndBeliefsDropdown.click()
if (values.political)
await this.selectOption(this.valuesAndBeliefsPolitics, values.political[0])
if (values.religious)
await this.selectOption(this.valuesAndBeliefsReligion, values.religious[0])
}
async setPersonalityFilter(personality: PersonalityFilter) {
await expect(this.personalityDropdown).toBeVisible()
await this.personalityDropdown.click()
if (personality.mbti) await this.selectOption(this.personalityMbti, personality.mbti)
if (personality.bigFive) {
await this.personalityBigFive.click()
if (personality.bigFive?.openness) {
await this.sliderHelper(
personality.bigFive.openness,
this.page.getByTestId('big-five-openness'),
)
}
if (personality.bigFive?.conscientiousness) {
await this.sliderHelper(
personality.bigFive.conscientiousness,
this.page.getByTestId('big-five-conscientiousness'),
)
}
if (personality.bigFive?.extraversion) {
await this.sliderHelper(
personality.bigFive.extraversion,
this.page.getByTestId('big-five-extraversion'),
)
}
if (personality.bigFive?.agreeableness) {
await this.sliderHelper(
personality.bigFive.agreeableness,
this.page.getByTestId('big-five-agreeableness'),
)
}
if (personality.bigFive?.neuroticism) {
await this.sliderHelper(
personality.bigFive.neuroticism,
this.page.getByTestId('big-five-neuroticism'),
)
}
}
}
async setAdvancedFilter(advanced: AdvancedFilter) {
await expect(this.advancedDropdown).toBeVisible()
await this.advancedDropdown.click()
if (advanced.lastActive) {
await this.advancedActive.click()
await this.page.getByRole('button', {name: `${advanced.lastActive[1]}`}).click()
}
if (advanced.photos) {
await this.advancedPhotos.click()
await this.page.getByRole('checkbox', {name: 'Has photos'}).click()
}
}
async setDisplayFilter(display: DisplayFilter) {
await expect(this.displayDropdown).toBeVisible()
await this.displayDropdown.click()
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]}`})
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
}
}
}
}
async getProfileInfo() {
await expect(this.profileGrid).toBeVisible()
const totalResults = await this.profileResults.count()
const chosenProfileNumber = Math.floor(Math.random() * totalResults)
const chosenProfile = await this.profileResults.nth(chosenProfileNumber)
const profileName = await chosenProfile.getByTestId('people-profile-name').textContent()
if (!profileName) return
return {
name: profileName,
}
}
async verifyNumberOfMatchingProfiles(count: number) {
await expect(this.profileCount).toBeVisible()
const test = await this.profileCount.textContent()
if (!test) return
expect(actual).toStrictEqual(expected)
}
}

View File

@@ -9,15 +9,31 @@ test.describe('when given valid input', () => {
await app.home.goToHomePage()
await app.home.verifySignedInHomePage(account.display_name)
})
test('the profile count should update sucessfully when applying a filter', async ({
app,
signedOutAccount: account,
}) => {
await app.signinWithEmail(account)
await app.home.clickPeopleLink()
await app.people.getProfileInfo()
const totalProfiles = await app.people.profileCountLocator.textContent()
await app.people.setConnectionTypeFilter(['Collaboration', 'collaboration'])
const filterdProfiles = await app.people.profileCountLocator.textContent()
if (!totalProfiles || !filterdProfiles) return
await expect(parseInt(totalProfiles)).not.toEqual(parseInt(filterdProfiles))
})
})
test.describe('when given invalid input', () => {
test('should not be able to sign in to an available account', async ({
app,
signedOutAccount,
signedOutAccount: account,
page,
}) => {
await app.signinWithEmail(signedOutAccount.email, 'ThisPassword', false)
await app.signinWithEmail(account.email, 'ThisPassword', false)
await expect(
page.getByText('Failed to sign in with your email and password', {exact: true}),
).toBeVisible()

View File

@@ -32,7 +32,7 @@ export type UserAccountInformation = {
height?: Height
ethnicity_origin?: EthnicityTuple
interested_in?: InterestedInGenderTuple
Interested_in_ages?: InterestedInAges
Interested_in_ages?: MinMaxNumbers
connection_type?: ConnectionTypeTuple
relationship_status?: RelationshipStatusTuple
relationship_style?: RelationshipStyleTuple
@@ -62,7 +62,7 @@ type Height = {
centimeters: string
}
type InterestedInAges = {
export type MinMaxNumbers = {
min: string
max?: string
}
@@ -89,7 +89,7 @@ export type Socials = {
urlOrUsername: string
}
type FiveBigPersonalityTraits = {
export type FiveBigPersonalityTraits = {
openness?: number
conscientiousness?: number
extraversion?: number

View File

@@ -70,7 +70,7 @@ export function Big5SliderRow(props: {
const {label, minValue, maxValue, onChange} = props
return (
<div className="mb-4">
<div className="mb-4" data-testid={`big-five-${label.toLowerCase()}`}>
<div className="mb-1 flex items-center justify-between text-sm text-ink-600">
<span>{label}</span>
<span className="font-semibold text-ink-700">

View File

@@ -9,7 +9,12 @@ import {Profile} from 'common/profiles/profile'
import {DisplayOptions} from 'common/profiles-rendering'
import {nullifyDictValues, removeNullOrUndefinedProps, sampleDictByPrefix} from 'common/util/object'
import {ReactNode, useState} from 'react'
import {Big5Filters, Big5FilterText, countBig5Filters, hasAnyBig5Filter,} from 'web/components/filters/big5-filter'
import {
Big5Filters,
Big5FilterText,
countBig5Filters,
hasAnyBig5Filter,
} from 'web/components/filters/big5-filter'
import {CardSizeSelector} from 'web/components/filters/card-size-selector'
import {DietFilter, DietFilterText} from 'web/components/filters/diet-filter'
import {EducationFilter, EducationFilterText} from 'web/components/filters/education-filter'
@@ -586,6 +591,7 @@ function Filters(props: {
</FilterSection>
<FilterSection
testId="lifestyle-smoker"
title={t('profile.optional.is_smoker', 'Smoker')}
openFilter={openFilter}
setOpenFilter={setOpenFilter}
@@ -742,6 +748,7 @@ function Filters(props: {
{/* LAST ACTIVE */}
<FilterSection
title={t('filter.last_active.title', 'Last active')}
testId="advanced-active"
openFilter={openFilter}
setOpenFilter={setOpenFilter}
isActive={!!filters.last_active}
@@ -810,6 +817,7 @@ export function FilterSection(props: {
selection?: ReactNode
showNewBadge?: boolean
newBadgeClassName?: string
testId?: string
}) {
const {
title,
@@ -823,10 +831,11 @@ export function FilterSection(props: {
selection,
showNewBadge,
newBadgeClassName,
testId,
} = props
const isOpen = openFilter == title
return (
<Col className={clsx(className)}>
<Col className={clsx(className)} data-testid={testId}>
<button
className={clsx(
'text-ink-600 flex w-full flex-row justify-between px-4 pt-4 relative hover-bold',

View File

@@ -267,7 +267,7 @@ export const Search = forwardRef<
</Row>
{(profileCount ?? 0) > 0 && (
<Row className="text-sm text-ink-500 gap-2">
<p>
<p data-testid="people-profile-count">
{profileCount}{' '}
{(profileCount ?? 0) > 1
? t('common.people', 'people')

View File

@@ -140,7 +140,11 @@ export function ControlledTabs(props: TabProps & {activeIndex: number}) {
<Row className={'items-center'}>
<Col>
{tab.title.split('\n').map((line, i) => (
<Row className={'items-center justify-center'} key={i}>
<Row
className={'items-center justify-center'}
key={i}
data-testid={`${tab.title.toLowerCase()}-tab`}
>
{line}
</Row>
))}

View File

@@ -120,7 +120,12 @@ const Profiles = {
href: '/',
icon: UsersIcon,
}
const Home = {key: 'nav.home', name: 'Home', href: '/', icon: HomeIcon}
const Home = {
key: 'nav.home',
name: 'Home',
href: '/',
icon: HomeIcon,
}
const faq = {
key: 'nav.faq',
name: 'FAQ',
@@ -163,7 +168,12 @@ const Organization = {
href: '/organization',
icon: GlobeAltIcon,
}
const Vote = {key: 'nav.vote', name: 'Vote', href: '/vote', icon: MdThumbUp}
const Vote = {
key: 'nav.vote',
name: 'Vote',
href: '/vote',
icon: MdThumbUp,
}
const Contact = {
key: 'nav.contact',
name: 'Contact',

View File

@@ -82,7 +82,7 @@ export const ProfileGrid = (props: {
}[cardSize ?? 'medium']
return (
<div className="relative">
<div className="relative" data-testid="people-profile-grid">
<div
className={clsx(
`grid gap-6 py-4 grid-cols-1`,
@@ -328,6 +328,7 @@ function ProfilePreview(props: {
isLoading && 'scale-[0.94] transition-transform duration-[80ms] ease-out',
!isLoading && 'transition-transform duration-[120ms] ease-in',
)}
data-testid="people-profile-results"
>
<Link
href={`/${user.username}`}
@@ -419,6 +420,7 @@ function ProfilePreview(props: {
'main-font font-medium text-lg text-gray-900 dark:text-white truncate my-0 transition-opacity duration-75',
isLoading && 'opacity-50',
)}
data-testid="people-profile-name"
>
{user.name}
</h3>
@@ -427,6 +429,7 @@ function ProfilePreview(props: {
'flex-wrap gap-x-2 transition-opacity duration-75',
isLoading && 'opacity-50',
)}
data-testid="people-profile-age-gender"
>
{showCity !== false && <ProfileLocation profile={profile} />}
{showAge !== false && profile.age && (