Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Update search term when navigating with browser`s back and forward buttons #2101

Merged
merged 12 commits into from
Feb 8, 2023
4 changes: 4 additions & 0 deletions src/components/VAllResultsGrid/VAllResultsGrid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@
v-if="item.frontendMediaType === 'image'"
:key="item.id"
:image="item"
:search-term="searchTerm"
/>
<VAudioCell
v-if="item.frontendMediaType === 'audio'"
:key="item.id"
:audio="item"
:search-term="searchTerm"
@interacted="hideSnackbar"
@focus.native="showSnackbar"
/>
Expand Down Expand Up @@ -82,6 +84,7 @@ export default defineComponent({
const i18n = useI18n()
const mediaStore = useMediaStore()
const searchStore = useSearchStore()
const searchTerm = computed(() => searchStore.searchTerm)

const resultsLoading = computed(() => {
return (
Expand Down Expand Up @@ -131,6 +134,7 @@ export default defineComponent({
}

return {
searchTerm,
isError,
errorHeader,
allMedia,
Expand Down
11 changes: 10 additions & 1 deletion src/components/VAllResultsGrid/VAudioCell.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<template>
<VAudioTrack :audio="audio" layout="box" v-on="$listeners" />
<VAudioTrack
:audio="audio"
layout="box"
:search-term="searchTerm"
v-on="$listeners"
/>
</template>

<script lang="ts">
Expand All @@ -16,6 +21,10 @@ export default defineComponent({
type: Object as PropType<AudioDetail>,
required: true,
},
searchTerm: {
type: String,
required: true,
},
},
})
</script>
6 changes: 5 additions & 1 deletion src/components/VAllResultsGrid/VImageCellSquare.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<VLink
itemprop="contentUrl"
:title="image.title"
:href="'/image/' + image.id"
:href="`/image/${image.id}?q=${searchTerm}`"
class="group block rounded-sm focus-bold-filled"
>
<figure
Expand Down Expand Up @@ -57,6 +57,10 @@ export default defineComponent({
type: Object as PropType<ImageDetail>,
required: true,
},
searchTerm: {
type: String,
required: true,
},
},
setup(props) {
const getImageUrl = () => {
Expand Down
10 changes: 9 additions & 1 deletion src/components/VAudioTrack/VAudioTrack.vue
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ export default defineComponent({
size: {
type: String as PropType<AudioSize>,
},
/**
* the search term that was used to find this track; This is used
* in the link to the track's detail page.
*/
searchTerm: {
type: String,
required: true,
},
},
emits: {
"shift-tab": defineEvent<[KeyboardEvent]>(),
Expand Down Expand Up @@ -441,7 +449,7 @@ export default defineComponent({
const layoutBasedProps = computed(() =>
isComposite.value
? {
href: `/audio/${props.audio.id}`,
href: `/audio/${props.audio.id}/?q=${props.searchTerm}`,
class: [
"cursor-pointer",
{
Expand Down
6 changes: 5 additions & 1 deletion src/components/VImageGrid/VImageCell.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<VLink
:href="'/image/' + image.id"
:href="`/image/${image.id}?q=${searchTerm}`"
class="group relative block w-full overflow-hidden rounded-sm bg-dark-charcoal-10 text-dark-charcoal-10 focus:outline-none focus:ring-[3px] focus:ring-pink focus:ring-offset-[3px]"
:aria-label="image.title"
:style="containerStyle"
Expand Down Expand Up @@ -65,6 +65,10 @@ export default defineComponent({
type: Object as PropType<ImageDetail>,
required: true,
},
searchTerm: {
type: String,
required: true,
},
},
setup(props) {
const imgHeight = ref(props.image.height || 100)
Expand Down
8 changes: 7 additions & 1 deletion src/components/VImageGrid/VImageGrid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
v-for="(image, index) in images"
:key="image.id"
:image="image"
:search-term="searchTerm"
@shift-tab="handleShiftTab($event, index)"
/>
</div>
Expand All @@ -27,6 +28,8 @@
*/
import { computed, defineComponent, PropType } from "@nuxtjs/composition-api"

import { useSearchStore } from "~/stores/search"

import type { FetchState } from "~/types/fetch-state"
import type { ImageDetail } from "~/types/media"

Expand Down Expand Up @@ -62,6 +65,9 @@ export default defineComponent({
"shift-tab": defineEvent(),
},
setup(props, { emit }) {
const searchStore = useSearchStore()

const searchTerm = computed(() => searchStore.searchTerm)
const isError = computed(() => Boolean(props.fetchState.fetchingError))

const handleShiftTab = (event: KeyboardEvent, index: number) => {
Expand All @@ -71,7 +77,7 @@ export default defineComponent({
}
}

return { isError, handleShiftTab }
return { isError, handleShiftTab, searchTerm }
},
})
</script>
Expand Down
4 changes: 4 additions & 0 deletions src/pages/audio/_id/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type { AudioDetail } from "~/types/media"
import { useRelatedMediaStore } from "~/stores/media/related-media"
import { useSingleResultStore } from "~/stores/media/single-result"
import { useFeatureFlagStore } from "~/stores/feature-flag"
import { useSearchStore } from "~/stores/search"
import { createDetailPageMeta } from "~/utils/og"

import VAudioDetails from "~/components/VAudioDetails/VAudioDetails.vue"
Expand All @@ -48,6 +49,9 @@ export default defineComponent({
if (from.path.includes("/search/")) {
to.meta.backToSearchPath = from.fullPath
}
if (from.path.includes("/search/") && to.query.q) {
useSearchStore().setSearchTerm(to.query.q)
}
next()
},
layout: "content-layout",
Expand Down
4 changes: 4 additions & 0 deletions src/pages/image/_id/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ import type { ImageDetail } from "~/types/media"
import { useSingleResultStore } from "~/stores/media/single-result"
import { useRelatedMediaStore } from "~/stores/media/related-media"
import { useFeatureFlagStore } from "~/stores/feature-flag"
import { useSearchStore } from "~/stores/search"
import { createDetailPageMeta } from "~/utils/og"

import VButton from "~/components/VButton.vue"
Expand Down Expand Up @@ -135,6 +136,9 @@ export default defineComponent({
if (from.path.includes("/search/")) {
to.meta.backToSearchPath = from.fullPath
}
if (from.path.includes("/search/") && to.query.q) {
useSearchStore().setSearchTerm(to.query.q)
}
next()
},
layout: "content-layout",
Expand Down
6 changes: 6 additions & 0 deletions src/pages/search/audio.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
:audio="audio"
:size="audioTrackSize"
layout="row"
:search-term="searchTerm"
@shift-tab="handleShiftTab($event, i)"
@interacted="hideSnackbar"
@mousedown.native="handleMouseDown"
Expand All @@ -43,6 +44,8 @@ import {
} from "@nuxtjs/composition-api"

import { useFocusFilters } from "~/composables/use-focus-filters"
import { useSearchStore } from "~/stores/search"

import { Focus } from "~/utils/focus-management"

import { useUiStore } from "~/stores/ui"
Expand Down Expand Up @@ -76,9 +79,11 @@ export default defineComponent({
? [{ hid: "robots", name: "robots", content: "all" }]
: undefined,
})
const searchStore = useSearchStore()

const uiStore = useUiStore()

const searchTerm = computed(() => searchStore.searchTerm)
const results = computed(() => props.resultItems.audio)

const isDesktopLayout = computed(() => uiStore.isDesktopLayout)
Expand Down Expand Up @@ -115,6 +120,7 @@ export default defineComponent({
}

return {
searchTerm,
results,
audioTrackSize,

Expand Down
10 changes: 5 additions & 5 deletions test/playwright/e2e/all-results-keyboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const walkToNextOfType = async (type: "image" | "audio", page: Page) => {
return page.evaluate(
([contextType]) =>
new RegExp(
`/${contextType}/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$`,
`/${contextType}/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\\?q=birds$`,
"i"
).test(
(document.activeElement as HTMLAnchorElement | null)?.href ?? ""
Expand Down Expand Up @@ -39,7 +39,7 @@ const locateFocusedResult = async (page: Page) => {
expect(href).toBeDefined()
const url = new URL(href ?? "")

return page.locator(`[href="${url.pathname}"]`)
return page.locator(`[href="${url.pathname}?q=birds"]`)
}

test.describe.configure({ mode: "parallel" })
Expand All @@ -54,7 +54,7 @@ test.describe("all results grid keyboard accessibility test", () => {
await page.keyboard.press("Enter")
await page.waitForURL(
new RegExp(
`/image/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$`,
`/image/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\\?q=birds$`,
"i"
)
)
Expand All @@ -65,13 +65,13 @@ test.describe("all results grid keyboard accessibility test", () => {
await page.keyboard.press("Enter")
await page.waitForURL(
new RegExp(
`/audio/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$`,
`/audio/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\\?q=birds$`,
"i"
)
)
expect(page.url()).toMatch(
new RegExp(
`/audio/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$`,
`/audio/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\\?q=birds$`,
"i"
)
)
Expand Down
95 changes: 95 additions & 0 deletions test/playwright/e2e/search-navigation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
openFilters,
searchFromHeader,
t,
openFirstResult,
} from "~~/test/playwright/utils/navigation"
import { mockProviderApis } from "~~/test/playwright/utils/route"
import breakpoints from "~~/test/playwright/utils/breakpoints"
Expand Down Expand Up @@ -128,3 +129,97 @@ test.describe("search history navigation", () => {
})
})
})

test.describe("search query param is set on a single page reulst", () => {
test.beforeEach(async ({ page }) => {
await page.goto(`/search?q=cat`)
})

test("the search query param should be set to the search term inside the header on a single page result of type image", async ({
page,
}) => {
await openFirstResult(page, "image")
const url = page.url()
const query = url.substring(url.indexOf("=") + 1)

expect(query).toEqual("cat")
})

test("the search query param should be set to the search term inside the header on a single page result of type audio", async ({
page,
}) => {
await openFirstResult(page, "audio")
const url = page.url()
const query = url.substring(url.indexOf("=") + 1)

expect(query).toEqual("cat")
})
})

test.describe(
"search term inside header should be set correctly when navigating back from a new search",
() => {
test.beforeEach(async ({ page }) => {
// search for cat
await page.goto(`/search?q=cat`)
})

/**
* # search for cat
* # navigate to the first image type result
* # search for dog
* # go back to single result page of cat
* # search term inside the header should be set to cat
*/
test("search term should be set to the search query param on an image type single result page", async ({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome test! It currently fails in staging :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so, does this test pass now?

page,
}) => {
await openFirstResult(page, "image")

let searchInput = page.locator('header input[type="search"]')
await searchInput.clear()

await searchInput.type("dog")

await Promise.all([
page.waitForNavigation(),
await page.getByRole("button", { name: "Search" }).click(),
])

await page.waitForLoadState("load")
await page.goBack()

searchInput = page.locator('header input[type="search"]')
const queryParam = page.url().substring(page.url().indexOf("=") + 1)

expect(queryParam).toEqual(await searchInput.inputValue())
})

/**
* same test as above, but the new serach is performed from an audio type single result page
*/
test("search term should be set to the search query param on an audio type single result page", async ({
page,
}) => {
await openFirstResult(page, "audio")

let searchInput = page.locator('header input[type="search"]')
await searchInput.clear()

await searchInput.type("dog")

await Promise.all([
page.waitForNavigation(),
await page.getByRole("button", { name: "Search" }).click(),
])

await page.waitForLoadState("load")
await page.goBack()

searchInput = page.locator('header input[type="search"]')
const queryParam = page.url().substring(page.url().indexOf("=") + 1)

expect(queryParam).toEqual(await searchInput.inputValue())
})
}
)