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

Commit

Permalink
External sources on no results page (#2077)
Browse files Browse the repository at this point in the history
* Use buttons for external sources links on the No results page

* Add VR tests

* Update src/components/VErrorSection/VNoResults.vue

* Add changes from code review

* Update snapshots
  • Loading branch information
obulat committed Jan 31, 2023
1 parent e88b3cb commit de7e3cb
Show file tree
Hide file tree
Showing 46 changed files with 154 additions and 64 deletions.
49 changes: 35 additions & 14 deletions src/components/VErrorSection/VNoResults.vue
Original file line number Diff line number Diff line change
@@ -1,35 +1,56 @@
<template>
<div class="no-results text-center md:text-left">
<h1 class="break-words text-4xl md:text-6xl">
{{ $t("no-results.heading", { query: query.q }) }}
<h1 class="heading-4 md:heading-2 break-words">
{{ $t("no-results.heading", { query: searchTerm }) }}
</h1>
<h2
class="mt-10 text-base font-normal md:mt-16 md:text-3xl md:font-semibold"
>
<h2 class="description-regular md:heading-5 mt-4">
{{ $t("no-results.alternatives") }}
</h2>
<VExternalSourceList class="mt-4 md:mt-6" :type="type" :query="query" />

<div class="mt-10 flex flex-col flex-wrap gap-4 gap-2 md:flex-row">
<VButton
v-for="source in externalSources"
:key="source.name"
as="VLink"
:href="source.url"
variant="secondary-bordered"
class="label-bold justify-between text-dark-charcoal md:justify-start md:gap-x-2"
>
{{ source.name }}
<VIcon :icon-path="externalLinkIcon" :size="4" rtl-flip />
</VButton>
</div>
</div>
</template>

<script lang="ts">
import { defineComponent, PropType } from "@nuxtjs/composition-api"
import type { MediaType } from "~/constants/media"
import type { ApiQueryParams } from "~/utils/search-query-transform"
import type { ExternalSource } from "~/types/external-source"
import VButton from "~/components/VButton.vue"
import VExternalSourceList from "~/components/VExternalSearch/VExternalSourceList.vue"
import VIcon from "~/components/VIcon/VIcon.vue"
import externalLinkIcon from "~/assets/icons/external-link.svg"
export default defineComponent({
name: "VNoResults",
components: { VExternalSourceList },
components: { VIcon, VButton },
props: {
type: {
type: String as PropType<MediaType>,
externalSources: {
type: Array as PropType<ExternalSource[]>,
required: true,
},
query: {
type: Object as PropType<ApiQueryParams>,
searchTerm: {
type: String,
required: true,
},
},
setup() {
return {
externalLinkIcon,
}
},
})
</script>
37 changes: 27 additions & 10 deletions src/components/VExternalSearch/VExternalSearchForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,12 @@
class="text-base font-normal leading-[130%]"
>
<template #type>{{ $t(`external-sources.form.types.${type}`) }}</template>
<template #query>{{ query.q }}</template>
<template #query>{{ searchTerm }}</template>
</i18n>

<VExternalSourceList
class="inline-flex ms-2 md:justify-center"
:type="type"
:query="query"
:external-sources="externalSources"
/>
</section>
</template>
Expand All @@ -49,23 +48,41 @@ import {
ref,
} from "@nuxtjs/composition-api"
import type { MediaType } from "~/constants/media"
import type { ApiQueryParams } from "~/utils/search-query-transform"
import { getFocusableElements } from "~/utils/focus-management"
import { defineEvent } from "~/types/emits"
import VExternalSourceList from "./VExternalSourceList.vue"
import type { MediaType } from "~/constants/media"
import type { ExternalSource } from "~/types/external-source"
import VExternalSourceList from "~/components/VExternalSearch/VExternalSourceList.vue"
export default defineComponent({
name: "VExternalSearchForm",
components: {
VExternalSourceList,
},
props: {
query: { type: Object as PropType<ApiQueryParams>, required: true },
type: { type: String as PropType<MediaType>, required: true },
isSupported: { type: Boolean, default: false },
hasNoResults: { type: Boolean, required: true },
type: {
type: String as PropType<MediaType>,
required: true,
},
searchTerm: {
type: String,
required: true,
},
externalSources: {
type: Array as PropType<ExternalSource[]>,
required: true,
},
isSupported: {
type: Boolean,
default: false,
},
hasNoResults: {
type: Boolean,
required: true,
},
},
emits: {
tab: defineEvent<[KeyboardEvent]>(),
Expand Down
24 changes: 8 additions & 16 deletions src/components/VExternalSearch/VExternalSourceList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
{{ $t("external-sources.caption", { openverse: "Openverse" }) }}
</p>
<VButton
v-for="source in sources"
v-for="source in externalSources"
:key="source.name"
as="VLink"
variant="plain"
Expand All @@ -60,11 +60,9 @@
</template>

<script lang="ts">
import { computed, defineComponent, PropType } from "@nuxtjs/composition-api"
import { defineComponent, PropType } from "@nuxtjs/composition-api"
import { getAdditionalSources } from "~/utils/get-additional-sources"
import type { ApiQueryParams } from "~/utils/search-query-transform"
import type { MediaType } from "~/constants/media"
import type { ExternalSource } from "~/types/external-source"
import VButton from "~/components/VButton.vue"
import VIcon from "~/components/VIcon/VIcon.vue"
Expand All @@ -86,24 +84,18 @@ export default defineComponent({
/**
* the media type to use as the criteria for filtering additional sources
*/
type: { type: String as PropType<MediaType>, required: true },
/**
* the search query to pre-populate in the additional sources link
*/
query: { type: Object as PropType<ApiQueryParams>, required: true },
externalSources: {
type: Array as PropType<ExternalSource[]>,
required: true,
},
},
setup(props) {
const sources = computed(() =>
getAdditionalSources(props.type, props.query)
)
setup() {
return {
icons: {
externalLink: externalLinkIcon,
caretDown: caretDownIcon,
closeSmall: closeSmallIcon,
},
sources,
}
},
})
Expand Down
47 changes: 32 additions & 15 deletions src/components/VSearchGrid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,43 @@
:class="isAllView ? 'mb-10' : 'mb-8'"
>
<VSearchResultsTitle :size="isAllView ? 'large' : 'default'">
{{ query.q }}
{{ searchTerm }}
</VSearchResultsTitle>
</header>

<slot name="media" />

<VExternalSearchForm
:type="metaSearchFormType"
:type="externalSourcesType"
:has-no-results="hasNoResults"
:query="query"
:external-sources="externalSources"
:search-term="searchTerm"
:is-supported="supported"
@tab="$emit('tab', $event)"
/>
</section>
<VErrorSection v-else class="w-full py-10">
<template #image>
<VErrorImage :error-code="NO_RESULT" />
<VErrorImage error-code="NO_RESULT" />
</template>
<VNoResults :type="metaSearchFormType" :query="query" />
<VNoResults :external-sources="externalSources" :search-term="searchTerm" />
</VErrorSection>
</template>

<script lang="ts">
import { computed, defineComponent, PropType } from "@nuxtjs/composition-api"
import { ALL_MEDIA, IMAGE, SearchType, mediaTypes } from "~/constants/media"
import {
ALL_MEDIA,
IMAGE,
SearchType,
isSupportedMediaType,
} from "~/constants/media"
import { NO_RESULT } from "~/constants/errors"
import { defineEvent } from "~/types/emits"
import type { ApiQueryParams } from "~/utils/search-query-transform"
import type { FetchState } from "~/types/fetch-state"
import type { ApiQueryParams } from "~/utils/search-query-transform"
import { getAdditionalSources } from "~/utils/get-additional-sources"
import VExternalSearchForm from "~/components/VExternalSearch/VExternalSearchForm.vue"
import VErrorSection from "~/components/VErrorSection/VErrorSection.vue"
Expand Down Expand Up @@ -88,29 +95,39 @@ export default defineComponent({
// noResult is hard-coded for search types that are not currently
// supported by Openverse built-in search
return props.supported
? props.query.q !== "" && props.resultsCount === 0
? Boolean(
props.query.q !== "" &&
props.fetchState.hasStarted &&
props.resultsCount === 0
)
: false
})
/**
* Metasearch form shows the external sources for current search type, or for images if the search type is 'All Content'.
* External sources search form shows the external sources for current search type, or for images if the search type is 'All Content'.
*/
const metaSearchFormType = computed(() => {
if (mediaTypes.includes(props.searchType)) {
const externalSourcesType = computed(() => {
if (isSupportedMediaType(props.searchType)) {
return props.searchType
}
return IMAGE
})
const isAllView = computed(() => {
return props.searchType === ALL_MEDIA
})
const isAllView = computed(() => props.searchType === ALL_MEDIA)
const externalSources = computed(() =>
getAdditionalSources(externalSourcesType.value, props.query)
)
const searchTerm = computed(() => props.query.q || "")
return {
hasNoResults,
metaSearchFormType,
externalSourcesType,
isAllView,
NO_RESULT,
externalSources,
searchTerm,
}
},
})
Expand Down
12 changes: 4 additions & 8 deletions src/components/VSearchResultsTitle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,16 @@
</h1>
</template>

<script>
import { defineComponent } from "@nuxtjs/composition-api"
<script lang="ts">
import { defineComponent, PropType } from "@nuxtjs/composition-api"
const sizes = {
DEFAULT: "default",
LARGE: "large",
}
export default defineComponent({
name: "VSearchResultsTitle",
props: {
size: {
required: false,
default: sizes.DEFAULT,
validator: (value) => Object.values(sizes).includes(value),
default: "default",
type: String as PropType<"default" | "large">,
},
},
})
Expand Down
5 changes: 5 additions & 0 deletions src/constants/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ export function isAdditionalSearchType(
return (additionalSearchTypes as readonly string[]).includes(searchType)
}

export function isSupportedMediaType(
searchType: string
): searchType is SupportedMediaType {
return supportedMediaTypes.includes(searchType as SupportedMediaType)
}
/* Media support */

export const SUPPORTED = "supported" // Native search
Expand Down
4 changes: 4 additions & 0 deletions src/types/external-source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface ExternalSource {
name: string
url: string
}
3 changes: 2 additions & 1 deletion test/playwright/utils/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
IMAGE,
MediaType,
MODEL_3D,
searchPath,
SupportedSearchType,
VIDEO,
} from "~/constants/media"
Expand Down Expand Up @@ -351,7 +352,7 @@ export const goToSearchTerm = async (
const headerMode = options.headerMode ?? NEW_HEADER

if (mode === "SSR") {
const path = `search/${searchTypePath(searchType)}?q=${term}${query}`
const path = `${searchPath(searchType)}?q=${term}${query}`
await page.goto(pathWithDir(path, dir))
await dismissTranslationBanner(page)
} else {
Expand Down
35 changes: 35 additions & 0 deletions test/playwright/visual-regression/pages/no-results.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { test } from "@playwright/test"

import {
enableNewHeader,
goToSearchTerm,
languageDirections,
setCookies,
} from "~~/test/playwright/utils/navigation"
import breakpoints from "~~/test/playwright/utils/breakpoints"

import { supportedSearchTypes } from "~/constants/media"

test.describe.configure({ mode: "parallel" })

for (const searchType of supportedSearchTypes) {
for (const dir of languageDirections) {
breakpoints.describeEvery(({ breakpoint, expectSnapshot }) => {
test(`No results ${searchType} ${dir} page snapshots`, async ({
page,
}) => {
await enableNewHeader(page)
await setCookies(page.context(), {
uiBreakpoint: breakpoint as string,
uiIsFilterDismissed: true,
uiDismissedBanners: ["translation-ar"],
})
await goToSearchTerm(page, "querywithnoresults", { dir, searchType })

await expectSnapshot(`no-results-${searchType}-${dir}`, page, {
fullPage: true,
})
})
})
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
"src/components/TableSortIcon.vue",
"src/components/VCopyButton.vue",
"src/components/VSketchFabViewer.vue",
"src/components/VSearchGrid.vue",

"src/components/VAudioThumbnail/**.vue",
"src/components/VAudioTrack/**.vue",
"src/components/VCheckbox/**.vue",
Expand Down

0 comments on commit de7e3cb

Please sign in to comment.