From 00c6f2566e0f325678da3d8f17a2e51f132490ba Mon Sep 17 00:00:00 2001 From: Okechi Jones-Williams <55924431+O-Bots@users.noreply.github.com> Date: Wed, 20 May 2026 19:02:00 +0100 Subject: [PATCH] [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 --------- Co-authored-by: MartinBraquet --- .github/workflows/cd-api.yml | 2 +- .github/workflows/ci.yml | 4 +- backend/email/emails/new-endorsement.tsx | 13 +- backend/email/emails/new-message.tsx | 2 +- common/src/choices.ts | 4 + tests/e2e/backend/utils/userInformation.ts | 18 +- tests/e2e/utils/firebaseUtils.ts | 5 +- tests/e2e/utils/seedDatabase.ts | 51 ++- tests/e2e/web/pages/app.ts | 6 + tests/e2e/web/pages/authPage.ts | 6 +- tests/e2e/web/pages/notificationsPage.ts | 11 + tests/e2e/web/pages/peoplePage.ts | 434 +++++++++++++++++++++ tests/e2e/web/specs/signIn.spec.ts | 20 +- tests/e2e/web/utils/accountInformation.ts | 6 +- web/components/filters/big5-filter.tsx | 2 +- web/components/filters/filters.tsx | 13 +- web/components/filters/search.tsx | 2 +- web/components/layout/tabs.tsx | 6 +- web/components/page-base.tsx | 14 +- web/components/profile-grid.tsx | 5 +- 20 files changed, 583 insertions(+), 41 deletions(-) create mode 100644 tests/e2e/web/pages/notificationsPage.ts create mode 100644 tests/e2e/web/pages/peoplePage.ts diff --git a/.github/workflows/cd-api.yml b/.github/workflows/cd-api.yml index a5d9d8d0..60a80806 100644 --- a/.github/workflows/cd-api.yml +++ b/.github/workflows/cd-api.yml @@ -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' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d57b393..41a35e36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: lint: diff --git a/backend/email/emails/new-endorsement.tsx b/backend/email/emails/new-endorsement.tsx index cc69f245..b6a65e14 100644 --- a/backend/email/emails/new-endorsement.tsx +++ b/backend/email/emails/new-endorsement.tsx @@ -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' diff --git a/backend/email/emails/new-message.tsx b/backend/email/emails/new-message.tsx index f2a293f5..df9ca041 100644 --- a/backend/email/emails/new-message.tsx +++ b/backend/email/emails/new-message.tsx @@ -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' diff --git a/common/src/choices.ts b/common/src/choices.ts index acc81247..35fcc5fc 100644 --- a/common/src/choices.ts +++ b/common/src/choices.ts @@ -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] diff --git a/tests/e2e/backend/utils/userInformation.ts b/tests/e2e/backend/utils/userInformation.ts index e159f217..10e687b2 100644 --- a/tests/e2e/backend/utils/userInformation.ts +++ b/tests/e2e/backend/utils/userInformation.ts @@ -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) { return array[Math.floor(Math.random() * array.length)].toLowerCase() diff --git a/tests/e2e/utils/firebaseUtils.ts b/tests/e2e/utils/firebaseUtils.ts index 96777d0b..c3dd114e 100644 --- a/tests/e2e/utils/firebaseUtils.ts +++ b/tests/e2e/utils/firebaseUtils.ts @@ -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 { +export async function firebaseUserExists( + email: string, + password: string, +): Promise { try { const login = await firebaseLoginEmailPassword(email, password) return login.data.localId diff --git a/tests/e2e/utils/seedDatabase.ts b/tests/e2e/utils/seedDatabase.ts index 3da70fd3..035b3b34 100644 --- a/tests/e2e/utils/seedDatabase.ts +++ b/tests/e2e/utils/seedDatabase.ts @@ -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 = diff --git a/tests/e2e/web/pages/app.ts b/tests/e2e/web/pages/app.ts index a5f25a1b..49048762 100644 --- a/tests/e2e/web/pages/app.ts +++ b/tests/e2e/web/pages/app.ts @@ -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() { diff --git a/tests/e2e/web/pages/authPage.ts b/tests/e2e/web/pages/authPage.ts index 7bccdf38..c4f82b94 100644 --- a/tests/e2e/web/pages/authPage.ts +++ b/tests/e2e/web/pages/authPage.ts @@ -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() { diff --git a/tests/e2e/web/pages/notificationsPage.ts b/tests/e2e/web/pages/notificationsPage.ts new file mode 100644 index 00000000..90c75aa4 --- /dev/null +++ b/tests/e2e/web/pages/notificationsPage.ts @@ -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') + } +} diff --git a/tests/e2e/web/pages/peoplePage.ts b/tests/e2e/web/pages/peoplePage.ts new file mode 100644 index 00000000..8c620f4d --- /dev/null +++ b/tests/e2e/web/pages/peoplePage.ts @@ -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) + } +} diff --git a/tests/e2e/web/specs/signIn.spec.ts b/tests/e2e/web/specs/signIn.spec.ts index 9501062f..537853fb 100644 --- a/tests/e2e/web/specs/signIn.spec.ts +++ b/tests/e2e/web/specs/signIn.spec.ts @@ -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() diff --git a/tests/e2e/web/utils/accountInformation.ts b/tests/e2e/web/utils/accountInformation.ts index bbb6b81f..2d2c4134 100644 --- a/tests/e2e/web/utils/accountInformation.ts +++ b/tests/e2e/web/utils/accountInformation.ts @@ -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 diff --git a/web/components/filters/big5-filter.tsx b/web/components/filters/big5-filter.tsx index 169a1446..3932f447 100644 --- a/web/components/filters/big5-filter.tsx +++ b/web/components/filters/big5-filter.tsx @@ -70,7 +70,7 @@ export function Big5SliderRow(props: { const {label, minValue, maxValue, onChange} = props return ( -
+
{label} diff --git a/web/components/filters/filters.tsx b/web/components/filters/filters.tsx index 5953c6a4..cd120641 100644 --- a/web/components/filters/filters.tsx +++ b/web/components/filters/filters.tsx @@ -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: { +