-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1667355
commit daac019
Showing
2 changed files
with
248 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,12 @@ interface WindowWithNostoJS extends Window { | |
[ | ||
callback: (api: { | ||
search: jest.Mock<ReturnType<NostoClient["search"]>> | ||
recordSearchSubmit: jest.Mock< | ||
ReturnType<NostoClient["recordSearchSubmit"]> | ||
> | ||
recordSearchClick: jest.Mock< | ||
ReturnType<NostoClient["recordSearchClick"]> | ||
> | ||
}) => unknown, | ||
] | ||
> | ||
|
@@ -79,15 +85,18 @@ const template = ` | |
{% for hit in response.products.hits %} | ||
<a | ||
class="ns-autocomplete-product" | ||
href="{{ hit.url }}" | ||
data-ns-hit="{{ hit | json | escape }}"> | ||
href="javascript:void(0);" | ||
data-ns-hit="{{ hit | json | escape }}" | ||
data-testid="product" | ||
> | ||
<img | ||
class="ns-autocomplete-product-image" | ||
src="{{ hit.imageUrl }}" | ||
alt="{{ hit.name }}" | ||
width="60" | ||
height="40" | ||
data-testid="product" /> | ||
class="ns-autocomplete-product-image" | ||
src="{{ hit.imageUrl }}" | ||
alt="{{ hit.name }}" | ||
width="60" | ||
height="40" | ||
/> | ||
<div class="ns-autocomplete-product-info"> | ||
{% if hit.brand %} | ||
<div class="ns-autocomplete-product-brand"> | ||
|
@@ -155,9 +164,19 @@ const handleAutocomplete = async ( | |
}) | ||
} | ||
|
||
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) | ||
|
||
const w = window as unknown as WindowWithNostoJS | ||
|
||
beforeAll(() => { | ||
let searchSpy: jest.Mock<ReturnType<NostoClient["search"]>> | ||
let recordSearchSubmitSpy: jest.Mock< | ||
ReturnType<NostoClient["recordSearchSubmit"]> | ||
> | ||
let recordSearchClickSpy: jest.Mock< | ||
ReturnType<NostoClient["recordSearchClick"]> | ||
> | ||
|
||
beforeEach(() => { | ||
document.body.innerHTML = ` | ||
<form id="search-form"> | ||
<input type="text" id="search" placeholder="search" data-testid="input" /> | ||
|
@@ -170,24 +189,44 @@ beforeAll(() => { | |
liquidScript.src = | ||
"https://cdn.jsdelivr.net/npm/[email protected]/dist/liquid.browser.min.js" | ||
document.body.appendChild(liquidScript) | ||
}) | ||
|
||
beforeEach(() => { | ||
const searchSpy = jest.fn( | ||
searchSpy = jest.fn( | ||
() => | ||
Promise.resolve(searchResponse) as unknown as ReturnType< | ||
NostoClient["search"] | ||
> | ||
) | ||
|
||
recordSearchSubmitSpy = jest.fn( | ||
() => | ||
Promise.resolve() as unknown as ReturnType< | ||
NostoClient["recordSearchSubmit"] | ||
> | ||
) | ||
|
||
recordSearchClickSpy = jest.fn( | ||
() => | ||
Promise.resolve() as unknown as ReturnType< | ||
NostoClient["recordSearchClick"] | ||
> | ||
) | ||
|
||
w.nostojs = jest.fn( | ||
( | ||
callback: (api: { | ||
search: jest.Mock<ReturnType<NostoClient["search"]>> | ||
recordSearchSubmit: jest.Mock< | ||
ReturnType<NostoClient["recordSearchSubmit"]> | ||
> | ||
recordSearchClick: jest.Mock< | ||
ReturnType<NostoClient["recordSearchClick"]> | ||
> | ||
}) => unknown | ||
) => | ||
callback({ | ||
search: searchSpy, | ||
recordSearchSubmit: recordSearchSubmitSpy, | ||
recordSearchClick: recordSearchClickSpy, | ||
}) | ||
) | ||
}) | ||
|
@@ -197,9 +236,6 @@ afterEach(() => { | |
const dropdown = screen.getByTestId("dropdown") | ||
const newElement = dropdown.cloneNode(true) | ||
dropdown?.parentNode?.replaceChild(newElement, dropdown) | ||
}) | ||
|
||
afterAll(() => { | ||
document.body.innerHTML = "" | ||
}) | ||
|
||
|
@@ -409,6 +445,159 @@ describe("fromLiquidTemplate", () => { | |
) | ||
}) | ||
}) | ||
|
||
describe("analytics", () => { | ||
it("should record search submit", async () => { | ||
const user = userEvent.setup() | ||
|
||
await waitFor(() => | ||
handleAutocomplete(fromLiquidTemplate(template)) | ||
) | ||
|
||
await user.type(screen.getByTestId("input"), "black") | ||
await user.click(screen.getByTestId("search-button")) | ||
|
||
await waitFor(() => | ||
expect(recordSearchSubmitSpy).toHaveBeenCalledWith("black") | ||
) | ||
}) | ||
|
||
it("should record search submit with keyboard", async () => { | ||
const user = userEvent.setup() | ||
|
||
await waitFor(() => | ||
handleAutocomplete(fromLiquidTemplate(template)) | ||
) | ||
|
||
await user.type(screen.getByTestId("input"), "black") | ||
await user.keyboard("{enter}") | ||
|
||
await waitFor(() => | ||
expect(recordSearchSubmitSpy).toHaveBeenCalledWith("black") | ||
) | ||
}) | ||
|
||
it("should record search click on keyword click", async () => { | ||
const user = userEvent.setup() | ||
|
||
await waitFor(() => | ||
handleAutocomplete(fromLiquidTemplate(template)) | ||
) | ||
|
||
await user.clear(screen.getByTestId("input")) | ||
await user.type(screen.getByTestId("input"), "black") | ||
|
||
await waitFor(async () => { | ||
await user.click(screen.getAllByTestId("keyword")?.[0]) | ||
expect(recordSearchClickSpy).toHaveBeenCalledWith( | ||
"autocomplete", | ||
{ | ||
_highlight: { | ||
keyword: "<strong>black</strong>", | ||
}, | ||
keyword: "black", | ||
} | ||
) | ||
}) | ||
}) | ||
|
||
it("should record search click on product click", async () => { | ||
const user = userEvent.setup() | ||
|
||
const oldLocation = w.location | ||
// @ts-expect-error: mock location | ||
delete w.location | ||
w.location = { ...oldLocation, assign: jest.fn(() => ({})) } | ||
|
||
await waitFor(() => | ||
handleAutocomplete(fromLiquidTemplate(template)) | ||
) | ||
|
||
await user.type(screen.getByTestId("input"), "black") | ||
await wait(500) | ||
await user.click(screen.getAllByTestId("product")?.[0]) | ||
|
||
await waitFor(() => { | ||
expect(recordSearchClickSpy).toHaveBeenCalledWith( | ||
"autocomplete", | ||
{ | ||
brand: "Brand", | ||
imageUrl: | ||
"https://cdn.shopify.com/s/files/1/0097/5821/2174/products/Perry-Ellis-mens-Slim-Fit-Washable-Suit-Pant-Suit-Pants-Black-black.jpg?v=1675970770", | ||
listPrice: 150, | ||
name: "Slim Fit Washable Suit Pant", | ||
price: 40, | ||
productId: "6853920686158", | ||
url: "https://www.perryellis.com/products/slim-fit-washable-suit-pant-black-4isb4316rt-010", | ||
} | ||
) | ||
}) | ||
}) | ||
|
||
it("should record search click on keyword submitted with keyboard", async () => { | ||
const user = userEvent.setup() | ||
|
||
await waitFor(() => | ||
handleAutocomplete(fromLiquidTemplate(template)) | ||
) | ||
|
||
await user.type(screen.getByTestId("input"), "black") | ||
await wait(500) | ||
await user.keyboard("{arrowdown}") | ||
await user.keyboard("{enter}") | ||
|
||
await waitFor(() => | ||
expect(recordSearchClickSpy).toHaveBeenCalledWith( | ||
"autocomplete", | ||
{ | ||
_highlight: { | ||
keyword: "<strong>black</strong>", | ||
}, | ||
keyword: "black", | ||
} | ||
) | ||
) | ||
}) | ||
|
||
it("should record search click on product submitted with keyboard", async () => { | ||
const user = userEvent.setup() | ||
|
||
const oldLocation = w.location | ||
// @ts-expect-error: mock location | ||
delete w.location | ||
w.location = { ...oldLocation, assign: jest.fn(() => ({})) } | ||
|
||
await waitFor(() => | ||
handleAutocomplete(fromLiquidTemplate(template)) | ||
) | ||
|
||
await user.type(screen.getByTestId("input"), "black") | ||
await wait(500) | ||
await user.keyboard("{arrowdown}") | ||
await user.keyboard("{arrowdown}") | ||
await user.keyboard("{arrowdown}") | ||
await user.keyboard("{arrowdown}") | ||
await user.keyboard("{arrowdown}") | ||
await user.keyboard("{arrowdown}") | ||
await user.keyboard("{enter}") | ||
|
||
await waitFor(() => { | ||
expect(recordSearchClickSpy).toHaveBeenCalledWith( | ||
"autocomplete", | ||
{ | ||
brand: "Brand", | ||
imageUrl: | ||
"https://cdn.shopify.com/s/files/1/0097/5821/2174/products/Perry-Ellis-mens-Slim-Fit-Washable-Suit-Pant-Suit-Pants-Black-black.jpg?v=1675970770", | ||
listPrice: 150, | ||
name: "Slim Fit Washable Suit Pant", | ||
price: 40, | ||
productId: "6853920686158", | ||
url: "https://www.perryellis.com/products/slim-fit-washable-suit-pant-black-4isb4316rt-010", | ||
} | ||
) | ||
}) | ||
}) | ||
}) | ||
}) | ||
|
||
describe("fromRemoteLiquidTemplate", () => { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,17 +7,23 @@ import { | |
fromMustacheTemplate, | ||
fromRemoteMustacheTemplate, | ||
} from "../src/mustache" | ||
import { NostoClient } from '../src/api/client' | ||
import { AutocompleteConfig } from '../src/config' | ||
import { DefaultState } from '../src/utils/state' | ||
import { autocomplete } from '../src/autocomplete' | ||
import { NostoClient } from "../src/api/client" | ||
import { AutocompleteConfig } from "../src/config" | ||
import { DefaultState } from "../src/utils/state" | ||
import { autocomplete } from "../src/autocomplete" | ||
|
||
interface WindowWithNostoJS extends Window { | ||
nostojs: jest.Mock< | ||
unknown, | ||
[ | ||
callback: (api: { | ||
search: jest.Mock<ReturnType<NostoClient["search"]>> | ||
recordSearchSubmit: jest.Mock< | ||
ReturnType<NostoClient["recordSearchSubmit"]> | ||
> | ||
recordSearchClick: jest.Mock< | ||
ReturnType<NostoClient["recordSearchClick"]> | ||
> | ||
}) => unknown, | ||
] | ||
> | ||
|
@@ -146,9 +152,19 @@ const handleAutocomplete = ( | |
}) | ||
} | ||
|
||
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) | ||
|
||
const w = window as unknown as WindowWithNostoJS | ||
|
||
beforeAll(() => { | ||
let searchSpy: jest.Mock<ReturnType<NostoClient["search"]>> | ||
let recordSearchSubmitSpy: jest.Mock< | ||
ReturnType<NostoClient["recordSearchSubmit"]> | ||
> | ||
let recordSearchClickSpy: jest.Mock< | ||
ReturnType<NostoClient["recordSearchClick"]> | ||
> | ||
|
||
beforeEach(() => { | ||
document.body.innerHTML = ` | ||
<form id="search-form"> | ||
<input type="text" id="search" placeholder="search" data-testid="input" /> | ||
|
@@ -160,24 +176,44 @@ beforeAll(() => { | |
const mustacheScript = document.createElement("script") | ||
mustacheScript.src = "https://unpkg.com/[email protected]/mustache.min.js" | ||
document.body.appendChild(mustacheScript) | ||
}) | ||
|
||
beforeEach(() => { | ||
const searchSpy = jest.fn( | ||
searchSpy = jest.fn( | ||
() => | ||
Promise.resolve(searchResponse) as unknown as ReturnType< | ||
NostoClient["search"] | ||
> | ||
) | ||
|
||
recordSearchSubmitSpy = jest.fn( | ||
() => | ||
Promise.resolve() as unknown as ReturnType< | ||
NostoClient["recordSearchSubmit"] | ||
> | ||
) | ||
|
||
recordSearchClickSpy = jest.fn( | ||
() => | ||
Promise.resolve() as unknown as ReturnType< | ||
NostoClient["recordSearchClick"] | ||
> | ||
) | ||
|
||
w.nostojs = jest.fn( | ||
( | ||
callback: (api: { | ||
search: jest.Mock<ReturnType<NostoClient["search"]>> | ||
recordSearchSubmit: jest.Mock< | ||
ReturnType<NostoClient["recordSearchSubmit"]> | ||
> | ||
recordSearchClick: jest.Mock< | ||
ReturnType<NostoClient["recordSearchClick"]> | ||
> | ||
}) => unknown | ||
) => | ||
callback({ | ||
search: searchSpy, | ||
recordSearchSubmit: recordSearchSubmitSpy, | ||
recordSearchClick: recordSearchClickSpy, | ||
}) | ||
) | ||
}) | ||
|