Skip to content

Commit 1810b9a

Browse files
authored
Merge pull request #306 from QAComet/qacomet/search-tests
Added tests for search
2 parents 1df8fc5 + 98b8ece commit 1810b9a

File tree

10 files changed

+270
-9
lines changed

10 files changed

+270
-9
lines changed

.github/scripts/medusa-config.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
const dotenv = require("dotenv");
2+
3+
let ENV_FILE_NAME = "";
4+
switch (process.env.NODE_ENV) {
5+
case "production":
6+
ENV_FILE_NAME = ".env.production";
7+
break;
8+
case "staging":
9+
ENV_FILE_NAME = ".env.staging";
10+
break;
11+
case "test":
12+
ENV_FILE_NAME = ".env.test";
13+
break;
14+
case "development":
15+
default:
16+
ENV_FILE_NAME = ".env";
17+
break;
18+
}
19+
20+
try {
21+
dotenv.config({ path: process.cwd() + "/" + ENV_FILE_NAME });
22+
} catch (e) {}
23+
24+
// CORS when consuming Medusa from admin
25+
const ADMIN_CORS =
26+
process.env.ADMIN_CORS || "http://localhost:7000,http://localhost:7001";
27+
28+
// CORS to avoid issues when consuming Medusa from a client
29+
const STORE_CORS = process.env.STORE_CORS || "http://localhost:8000";
30+
31+
const DATABASE_URL =
32+
process.env.DATABASE_URL || "postgres://medusa:password@localhost/medusa";
33+
34+
const REDIS_URL = process.env.REDIS_URL || "redis://localhost:6379";
35+
36+
const plugins = [
37+
`medusa-fulfillment-manual`,
38+
`medusa-payment-manual`,
39+
{
40+
resolve: `@medusajs/file-local`,
41+
options: {
42+
upload_dir: "uploads",
43+
},
44+
},
45+
{
46+
resolve: "@medusajs/admin",
47+
/** @type {import('@medusajs/admin').PluginOptions} */
48+
options: {
49+
autoRebuild: true,
50+
develop: {
51+
open: process.env.OPEN_BROWSER !== "false",
52+
},
53+
},
54+
},
55+
{
56+
resolve: `medusa-plugin-meilisearch`,
57+
options: {
58+
config: {
59+
host: process.env.MEILISEARCH_HOST,
60+
apiKey: process.env.MEILISEARCH_API_KEY,
61+
},
62+
settings: {
63+
products: {
64+
indexSettings: {
65+
searchableAttributes: [
66+
"title",
67+
"description",
68+
"variant_sku",
69+
],
70+
displayedAttributes: [
71+
"id",
72+
"title",
73+
"description",
74+
"variant_sku",
75+
"thumbnail",
76+
"handle",
77+
],
78+
},
79+
primaryKey: "id",
80+
},
81+
},
82+
},
83+
},
84+
];
85+
86+
const modules = {
87+
/*eventBus: {
88+
resolve: "@medusajs/event-bus-redis",
89+
options: {
90+
redisUrl: REDIS_URL
91+
}
92+
},
93+
cacheService: {
94+
resolve: "@medusajs/cache-redis",
95+
options: {
96+
redisUrl: REDIS_URL
97+
}
98+
},*/
99+
};
100+
101+
/** @type {import('@medusajs/medusa').ConfigModule["projectConfig"]} */
102+
const projectConfig = {
103+
jwtSecret: process.env.JWT_SECRET,
104+
cookieSecret: process.env.COOKIE_SECRET,
105+
store_cors: STORE_CORS,
106+
database_url: DATABASE_URL,
107+
admin_cors: ADMIN_CORS,
108+
// Uncomment the following lines to enable REDIS
109+
redis_url: REDIS_URL
110+
};
111+
112+
/** @type {import('@medusajs/medusa').ConfigModule} */
113+
module.exports = {
114+
projectConfig,
115+
plugins,
116+
modules,
117+
};

.github/workflows/test-e2e.yaml

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,16 @@ env:
2929
DATABASE_TYPE: "postgres"
3030
REDIS_URL: redis://localhost:6379
3131
DATABASE_URL: postgres://test_medusa_user:password@localhost/test_medusa_db
32+
MEILISEARCH_HOST: http://localhost:7700
33+
MEILISEARCH_API_KEY: meili_api_key
34+
3235
NEXT_PUBLIC_BASE_URL: http://localhost:8000
36+
NEXT_PUBLIC_DEFAULT_REGION: us
37+
NEXT_PUBLIC_MEDUSA_BACKEND_URL: http://localhost:9000
38+
NEXT_PUBLIC_INDEX_NAME: products
39+
NEXT_PUBLIC_SEARCH_ENDPOINT: http://127.0.0.1:7700
40+
NEXT_PUBLIC_SEARCH_API_KEY: meili_api_key
41+
REVALIDATE_SECRET: supersecret
3342

3443
jobs:
3544
e2e-test-runner:
@@ -53,6 +62,9 @@ jobs:
5362
5463
meilisearch:
5564
image: getmeili/meilisearch:v1.7
65+
env:
66+
MEILI_MASTER_KEY: meili_api_key
67+
MEILI_ENV: development
5668
ports:
5769
- 7700:7700
5870
options: >-
@@ -100,11 +112,18 @@ jobs:
100112
--db-database ${{ env.TEST_POSTGRES_DATABASE }} \
101113
--db-host ${{ env.TEST_POSTGRES_HOST }} \
102114
--db-port ${{ env.TEST_POSTGREST_PORT }}
103-
115+
104116
- name: Build the backend
105117
working-directory: ../backend
106118
run: yarn build:admin
107119

120+
- name: Setup search in the backend
121+
working-directory: ../backend
122+
run: yarn add medusa-plugin-meilisearch
123+
124+
- name: Move custom medusa config to the backend
125+
run: cp .github/scripts/medusa-config.js ../backend/medusa-config.js
126+
108127
- name: Seed data from default seed file
109128
working-directory: ../backend
110129
run: medusa seed --seed-file=data/seed.json
@@ -119,9 +138,6 @@ jobs:
119138
- name: Install playwright
120139
run: yarn playwright install --with-deps
121140

122-
- name: Copy environment
123-
run: cp .env.template .env
124-
125141
- name: Setup frontend
126142
run: yarn build
127143

e2e/fixtures/base/base-page.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { CartDropdown } from "./cart-dropdown"
22
import { NavMenu } from "./nav-menu"
33
import { Page, Locator } from "@playwright/test"
4+
import { SearchModal } from "./search-modal"
45

56
export class BasePage {
67
page: Page
78
navMenu: NavMenu
89
cartDropdown: CartDropdown
10+
searchModal: SearchModal
911
accountLink: Locator
1012
searchLink: Locator
1113
storeLink: Locator
@@ -15,6 +17,7 @@ export class BasePage {
1517
this.page = page
1618
this.navMenu = new NavMenu(page)
1719
this.cartDropdown = new CartDropdown(page)
20+
this.searchModal = new SearchModal(page)
1821
this.accountLink = page.getByTestId("nav-account-link")
1922
this.storeLink = page.getByTestId("nav-store-link")
2023
this.searchLink = page.getByTestId("nav-search-link")

e2e/fixtures/base/search-modal.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Page, Locator } from "@playwright/test"
2+
import { BaseModal } from "./base-modal"
3+
import { NavMenu } from "./nav-menu"
4+
5+
export class SearchModal extends BaseModal {
6+
searchInput: Locator
7+
searchResults: Locator
8+
noSearchResultsContainer: Locator
9+
searchResult: Locator
10+
searchResultTitle: Locator
11+
12+
constructor(page: Page) {
13+
super(page, page.getByTestId("search-modal-container"))
14+
this.searchInput = this.container.getByTestId("search-input")
15+
this.searchResults = this.container.getByTestId("search-results")
16+
this.noSearchResultsContainer = this.container.getByTestId(
17+
"no-search-results-container"
18+
)
19+
this.searchResult = this.container.getByTestId("search-result")
20+
this.searchResultTitle = this.container.getByTestId("search-result-title")
21+
}
22+
23+
async open() {
24+
const menu = new NavMenu(this.page)
25+
await menu.open()
26+
await menu.searchLink.click()
27+
await this.container.waitFor({ state: "visible" })
28+
}
29+
30+
async close() {
31+
const viewport = this.page.viewportSize()
32+
const y = viewport ? viewport.height / 2 : 100
33+
await this.page.mouse.click(1, y, { clickCount: 2, delay: 100 })
34+
await this.container.waitFor({ state: "hidden" })
35+
}
36+
}

e2e/tests/public/search.spec.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { test, expect } from "../../index"
2+
3+
test.describe("Search tests", async () => {
4+
test("Searching for a specific product returns the correct product page", async ({
5+
productPage,
6+
}) => {
7+
const searchModal = productPage.searchModal
8+
await searchModal.open()
9+
await searchModal.searchInput.fill("Sweatshirt")
10+
await searchModal.searchResult
11+
.filter({ hasText: "Sweatshirt" })
12+
.first()
13+
.click()
14+
await productPage.container.waitFor({ state: "visible" })
15+
await expect(productPage.productTitle).toContainText("Sweatshirt")
16+
})
17+
18+
test("An erroneous search returns an empty result", async ({
19+
productPage,
20+
}) => {
21+
const searchModal = productPage.searchModal
22+
await searchModal.open()
23+
await searchModal.searchInput.fill("Does Not Sweatshirt")
24+
await expect(searchModal.noSearchResultsContainer).toBeVisible()
25+
})
26+
27+
test("User can search after an empty search result", async ({
28+
productPage,
29+
}) => {
30+
const searchModal = productPage.searchModal
31+
32+
await searchModal.open()
33+
await searchModal.searchInput.fill("Does Not Sweatshirt")
34+
await expect(searchModal.noSearchResultsContainer).toBeVisible()
35+
36+
await searchModal.searchInput.fill("Sweat")
37+
await expect(searchModal.searchResults).toBeVisible()
38+
await expect(searchModal.searchResult.first()).toBeVisible()
39+
})
40+
41+
test("Closing the search page returns user back to their current page", async ({
42+
storePage,
43+
productPage,
44+
loginPage,
45+
}) => {
46+
const searchModal = storePage.searchModal
47+
await test.step("Navigate to the store page and open and close search modal", async () => {
48+
await storePage.goto()
49+
await searchModal.open()
50+
await searchModal.close()
51+
await expect(storePage.container).toBeVisible()
52+
})
53+
54+
await test.step("Navigate to the product page and open and close search modal", async () => {
55+
await storePage.goto()
56+
const product = await storePage.getProduct("Sweatshirt")
57+
await product.locator.click()
58+
await productPage.container.waitFor({ state: "visible" })
59+
await searchModal.open()
60+
await searchModal.close()
61+
await expect(productPage.container).toBeVisible()
62+
})
63+
64+
await test.step("Navigate to the login page and open and close search modal", async () => {
65+
await loginPage.goto()
66+
await searchModal.open()
67+
await searchModal.close()
68+
await expect(loginPage.container).toBeVisible()
69+
})
70+
})
71+
})

src/modules/search/components/hit/index.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ type HitProps = {
2121

2222
const Hit = ({ hit }: HitProps) => {
2323
return (
24-
<LocalizedClientLink href={`/products/${hit.handle}`}>
24+
<LocalizedClientLink
25+
href={`/products/${hit.handle}`}
26+
data-testid="search-result"
27+
>
2528
<Container
2629
key={hit.id}
2730
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"
@@ -33,7 +36,12 @@ const Hit = ({ hit }: HitProps) => {
3336
/>
3437
<div className="flex flex-col justify-between group">
3538
<div className="flex flex-col">
36-
<Text className="text-ui-fg-subtle">{hit.title}</Text>
39+
<Text
40+
className="text-ui-fg-subtle"
41+
data-testid="search-result-title"
42+
>
43+
{hit.title}
44+
</Text>
3745
</div>
3846
</div>
3947
</Container>

src/modules/search/components/hits/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ const Hits = ({
3333
}
3434
)}
3535
>
36-
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-4">
36+
<div
37+
className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-4"
38+
data-testid="search-results"
39+
>
3740
{hits.slice(0, 6).map((hit, index) => (
3841
<li
3942
key={index}

src/modules/search/components/search-box/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const ControlledSearchBox = ({
4545
<div className="flex items-center justify-between">
4646
<input
4747
ref={inputRef}
48+
data-testid="search-input"
4849
autoComplete="off"
4950
autoCorrect="off"
5051
autoCapitalize="off"

src/modules/search/components/show-all/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ const ShowAll = () => {
1313

1414
if (hits.length === 0) {
1515
return (
16-
<Container className="flex gap-2 justify-center h-fit py-2">
16+
<Container
17+
className="flex gap-2 justify-center h-fit py-2"
18+
data-testid="no-search-results-container"
19+
>
1720
<Text>No results found.</Text>
1821
</Container>
1922
)

src/modules/search/templates/search-modal/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ export default function SearchModal() {
6363
indexName={SEARCH_INDEX_NAME}
6464
searchClient={searchClient}
6565
>
66-
<div className="flex absolute flex-col h-fit w-full sm:w-fit">
66+
<div
67+
className="flex absolute flex-col h-fit w-full sm:w-fit"
68+
data-testid="search-modal-container"
69+
>
6770
<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">
6871
<MagnifyingGlassMini />
6972
<SearchBox />

0 commit comments

Comments
 (0)