Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added tests for search #306

Merged
merged 4 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions .github/scripts/medusa-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
const dotenv = require("dotenv");

let ENV_FILE_NAME = "";
switch (process.env.NODE_ENV) {
case "production":
ENV_FILE_NAME = ".env.production";
break;
case "staging":
ENV_FILE_NAME = ".env.staging";
break;
case "test":
ENV_FILE_NAME = ".env.test";
break;
case "development":
default:
ENV_FILE_NAME = ".env";
break;
}

try {
dotenv.config({ path: process.cwd() + "/" + ENV_FILE_NAME });
} catch (e) {}

// CORS when consuming Medusa from admin
const ADMIN_CORS =
process.env.ADMIN_CORS || "http://localhost:7000,http://localhost:7001";

// CORS to avoid issues when consuming Medusa from a client
const STORE_CORS = process.env.STORE_CORS || "http://localhost:8000";

const DATABASE_URL =
process.env.DATABASE_URL || "postgres://medusa:password@localhost/medusa";

const REDIS_URL = process.env.REDIS_URL || "redis://localhost:6379";

const plugins = [
`medusa-fulfillment-manual`,
`medusa-payment-manual`,
{
resolve: `@medusajs/file-local`,
options: {
upload_dir: "uploads",
},
},
{
resolve: "@medusajs/admin",
/** @type {import('@medusajs/admin').PluginOptions} */
options: {
autoRebuild: true,
develop: {
open: process.env.OPEN_BROWSER !== "false",
},
},
},
{
resolve: `medusa-plugin-meilisearch`,
options: {
config: {
host: process.env.MEILISEARCH_HOST,
apiKey: process.env.MEILISEARCH_API_KEY,
},
settings: {
products: {
indexSettings: {
searchableAttributes: [
"title",
"description",
"variant_sku",
],
displayedAttributes: [
"id",
"title",
"description",
"variant_sku",
"thumbnail",
"handle",
],
},
primaryKey: "id",
},
},
},
},
];

const modules = {
/*eventBus: {
resolve: "@medusajs/event-bus-redis",
options: {
redisUrl: REDIS_URL
}
},
cacheService: {
resolve: "@medusajs/cache-redis",
options: {
redisUrl: REDIS_URL
}
},*/
};

/** @type {import('@medusajs/medusa').ConfigModule["projectConfig"]} */
const projectConfig = {
jwtSecret: process.env.JWT_SECRET,
cookieSecret: process.env.COOKIE_SECRET,
store_cors: STORE_CORS,
database_url: DATABASE_URL,
admin_cors: ADMIN_CORS,
// Uncomment the following lines to enable REDIS
redis_url: REDIS_URL
};

/** @type {import('@medusajs/medusa').ConfigModule} */
module.exports = {
projectConfig,
plugins,
modules,
};
24 changes: 20 additions & 4 deletions .github/workflows/test-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,16 @@ env:
DATABASE_TYPE: "postgres"
REDIS_URL: redis://localhost:6379
DATABASE_URL: postgres://test_medusa_user:password@localhost/test_medusa_db
MEILISEARCH_HOST: http://localhost:7700
MEILISEARCH_API_KEY: meili_api_key

NEXT_PUBLIC_BASE_URL: http://localhost:8000
NEXT_PUBLIC_DEFAULT_REGION: us
NEXT_PUBLIC_MEDUSA_BACKEND_URL: http://localhost:9000
NEXT_PUBLIC_INDEX_NAME: products
NEXT_PUBLIC_SEARCH_ENDPOINT: http://127.0.0.1:7700
NEXT_PUBLIC_SEARCH_API_KEY: meili_api_key
REVALIDATE_SECRET: supersecret

jobs:
e2e-test-runner:
Expand All @@ -53,6 +62,9 @@ jobs:

meilisearch:
image: getmeili/meilisearch:v1.7
env:
MEILI_MASTER_KEY: meili_api_key
MEILI_ENV: development
ports:
- 7700:7700
options: >-
Expand Down Expand Up @@ -100,11 +112,18 @@ jobs:
--db-database ${{ env.TEST_POSTGRES_DATABASE }} \
--db-host ${{ env.TEST_POSTGRES_HOST }} \
--db-port ${{ env.TEST_POSTGREST_PORT }}

- name: Build the backend
working-directory: ../backend
run: yarn build:admin

- name: Setup search in the backend
working-directory: ../backend
run: yarn add medusa-plugin-meilisearch

- name: Move custom medusa config to the backend
run: cp .github/scripts/medusa-config.js ../backend/medusa-config.js

- name: Seed data from default seed file
working-directory: ../backend
run: medusa seed --seed-file=data/seed.json
Expand All @@ -119,9 +138,6 @@ jobs:
- name: Install playwright
run: yarn playwright install --with-deps

- name: Copy environment
run: cp .env.template .env

- name: Setup frontend
run: yarn build

Expand Down
3 changes: 3 additions & 0 deletions e2e/fixtures/base/base-page.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { CartDropdown } from "./cart-dropdown"
import { NavMenu } from "./nav-menu"
import { Page, Locator } from "@playwright/test"
import { SearchModal } from "./search-modal"

export class BasePage {
page: Page
navMenu: NavMenu
cartDropdown: CartDropdown
searchModal: SearchModal
accountLink: Locator
searchLink: Locator
storeLink: Locator
Expand All @@ -15,6 +17,7 @@ export class BasePage {
this.page = page
this.navMenu = new NavMenu(page)
this.cartDropdown = new CartDropdown(page)
this.searchModal = new SearchModal(page)
this.accountLink = page.getByTestId("nav-account-link")
this.storeLink = page.getByTestId("nav-store-link")
this.searchLink = page.getByTestId("nav-search-link")
Expand Down
36 changes: 36 additions & 0 deletions e2e/fixtures/base/search-modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Page, Locator } from "@playwright/test"
import { BaseModal } from "./base-modal"
import { NavMenu } from "./nav-menu"

export class SearchModal extends BaseModal {
searchInput: Locator
searchResults: Locator
noSearchResultsContainer: Locator
searchResult: Locator
searchResultTitle: Locator

constructor(page: Page) {
super(page, page.getByTestId("search-modal-container"))
this.searchInput = this.container.getByTestId("search-input")
this.searchResults = this.container.getByTestId("search-results")
this.noSearchResultsContainer = this.container.getByTestId(
"no-search-results-container"
)
this.searchResult = this.container.getByTestId("search-result")
this.searchResultTitle = this.container.getByTestId("search-result-title")
}

async open() {
const menu = new NavMenu(this.page)
await menu.open()
await menu.searchLink.click()
await this.container.waitFor({ state: "visible" })
}

async close() {
const viewport = this.page.viewportSize()
const y = viewport ? viewport.height / 2 : 100
await this.page.mouse.click(1, y, { clickCount: 2, delay: 100 })
await this.container.waitFor({ state: "hidden" })
}
}
71 changes: 71 additions & 0 deletions e2e/tests/public/search.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { test, expect } from "../../index"

test.describe("Search tests", async () => {
test("Searching for a specific product returns the correct product page", async ({
productPage,
}) => {
const searchModal = productPage.searchModal
await searchModal.open()
await searchModal.searchInput.fill("Sweatshirt")
await searchModal.searchResult
.filter({ hasText: "Sweatshirt" })
.first()
.click()
await productPage.container.waitFor({ state: "visible" })
await expect(productPage.productTitle).toContainText("Sweatshirt")
})

test("An erroneous search returns an empty result", async ({
productPage,
}) => {
const searchModal = productPage.searchModal
await searchModal.open()
await searchModal.searchInput.fill("Does Not Sweatshirt")
await expect(searchModal.noSearchResultsContainer).toBeVisible()
})

test("User can search after an empty search result", async ({
productPage,
}) => {
const searchModal = productPage.searchModal

await searchModal.open()
await searchModal.searchInput.fill("Does Not Sweatshirt")
await expect(searchModal.noSearchResultsContainer).toBeVisible()

await searchModal.searchInput.fill("Sweat")
await expect(searchModal.searchResults).toBeVisible()
await expect(searchModal.searchResult.first()).toBeVisible()
})

test("Closing the search page returns user back to their current page", async ({
storePage,
productPage,
loginPage,
}) => {
const searchModal = storePage.searchModal
await test.step("Navigate to the store page and open and close search modal", async () => {
await storePage.goto()
await searchModal.open()
await searchModal.close()
await expect(storePage.container).toBeVisible()
})

await test.step("Navigate to the product page and open and close search modal", async () => {
await storePage.goto()
const product = await storePage.getProduct("Sweatshirt")
await product.locator.click()
await productPage.container.waitFor({ state: "visible" })
await searchModal.open()
await searchModal.close()
await expect(productPage.container).toBeVisible()
})

await test.step("Navigate to the login page and open and close search modal", async () => {
await loginPage.goto()
await searchModal.open()
await searchModal.close()
await expect(loginPage.container).toBeVisible()
})
})
})
12 changes: 10 additions & 2 deletions src/modules/search/components/hit/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ type HitProps = {

const Hit = ({ hit }: HitProps) => {
return (
<LocalizedClientLink href={`/products/${hit.handle}`}>
<LocalizedClientLink
href={`/products/${hit.handle}`}
data-testid="search-result"
>
<Container
key={hit.id}
className="flex sm:flex-col gap-2 w-full p-4 shadow-elevation-card-rest hover:shadow-elevation-card-hover items-center sm:justify-center"
Expand All @@ -33,7 +36,12 @@ const Hit = ({ hit }: HitProps) => {
/>
<div className="flex flex-col justify-between group">
<div className="flex flex-col">
<Text className="text-ui-fg-subtle">{hit.title}</Text>
<Text
className="text-ui-fg-subtle"
data-testid="search-result-title"
>
{hit.title}
</Text>
</div>
</div>
</Container>
Expand Down
5 changes: 4 additions & 1 deletion src/modules/search/components/hits/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ const Hits = ({
}
)}
>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-4">
<div
className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-4"
data-testid="search-results"
>
{hits.slice(0, 6).map((hit, index) => (
<li
key={index}
Expand Down
1 change: 1 addition & 0 deletions src/modules/search/components/search-box/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const ControlledSearchBox = ({
<div className="flex items-center justify-between">
<input
ref={inputRef}
data-testid="search-input"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
Expand Down
5 changes: 4 additions & 1 deletion src/modules/search/components/show-all/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ const ShowAll = () => {

if (hits.length === 0) {
return (
<Container className="flex gap-2 justify-center h-fit py-2">
<Container
className="flex gap-2 justify-center h-fit py-2"
data-testid="no-search-results-container"
>
<Text>No results found.</Text>
</Container>
)
Expand Down
5 changes: 4 additions & 1 deletion src/modules/search/templates/search-modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ export default function SearchModal() {
indexName={SEARCH_INDEX_NAME}
searchClient={searchClient}
>
<div className="flex absolute flex-col h-fit w-full sm:w-fit">
<div
className="flex absolute flex-col h-fit w-full sm:w-fit"
data-testid="search-modal-container"
>
<div className="w-full flex items-center gap-x-2 p-4 bg-[rgba(3,7,18,0.5)] text-ui-fg-on-color backdrop-blur-2xl rounded-rounded">
<MagnifyingGlassMini />
<SearchBox />
Expand Down
Loading