Support metadata icons field (#45105)
NEXT-400 ## Feature - [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [x] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md)
This commit is contained in:
parent
ff1664b11b
commit
45a9373113
15 changed files with 295 additions and 54 deletions
|
@ -1,7 +1,7 @@
|
||||||
import type { ResolvedMetadata } from '../types/metadata-interface'
|
import type { ResolvedMetadata } from '../types/metadata-interface'
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Meta } from './utils'
|
import { Meta } from './meta'
|
||||||
|
|
||||||
export function ResolvedBasicMetadata({
|
export function ResolvedBasicMetadata({
|
||||||
metadata,
|
metadata,
|
||||||
|
|
69
packages/next/src/lib/metadata/generate/icons.tsx
Normal file
69
packages/next/src/lib/metadata/generate/icons.tsx
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import type { ResolvedMetadata } from '../types/metadata-interface'
|
||||||
|
import type { Icon, IconDescriptor } from '../types/metadata-types'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const resolveUrl = (url: string | URL) =>
|
||||||
|
typeof url === 'string' ? url : url.toString()
|
||||||
|
|
||||||
|
function IconDescriptorLink({ icon }: { icon: IconDescriptor }) {
|
||||||
|
const { url, rel = 'icon', ...props } = icon
|
||||||
|
|
||||||
|
return <link rel={rel} href={resolveUrl(url)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconLink({ rel, icon }: { rel?: string; icon: Icon }) {
|
||||||
|
if (typeof icon === 'object' && !(icon instanceof URL)) {
|
||||||
|
if (rel) icon.rel = rel
|
||||||
|
return <IconDescriptorLink icon={icon} />
|
||||||
|
} else {
|
||||||
|
const href = resolveUrl(icon)
|
||||||
|
return <link rel={rel} href={href} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ResolvedIconsMetadata({
|
||||||
|
icons,
|
||||||
|
}: {
|
||||||
|
icons: ResolvedMetadata['icons']
|
||||||
|
}) {
|
||||||
|
if (!icons) return null
|
||||||
|
|
||||||
|
const shortcutList = icons.shortcut
|
||||||
|
const iconList = icons.icon
|
||||||
|
const appleList = icons.apple
|
||||||
|
const otherList = icons.other
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{shortcutList
|
||||||
|
? shortcutList.map((icon, index) => (
|
||||||
|
<IconLink
|
||||||
|
key={`shortcut-${index}`}
|
||||||
|
rel="shortcut icon"
|
||||||
|
icon={icon}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
: null}
|
||||||
|
{iconList
|
||||||
|
? iconList.map((icon, index) => (
|
||||||
|
<IconLink key={`shortcut-${index}`} rel="icon" icon={icon} />
|
||||||
|
))
|
||||||
|
: null}
|
||||||
|
{appleList
|
||||||
|
? appleList.map((icon, index) => (
|
||||||
|
<IconLink
|
||||||
|
key={`apple-${index}`}
|
||||||
|
rel="apple-touch-icon"
|
||||||
|
icon={icon}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
: null}
|
||||||
|
{otherList
|
||||||
|
? otherList.map((icon, index) => (
|
||||||
|
<IconDescriptorLink key={`other-${index}`} icon={icon} />
|
||||||
|
))
|
||||||
|
: null}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import type { ResolvedMetadata } from '../types/metadata-interface'
|
import type { ResolvedMetadata } from '../types/metadata-interface'
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Meta, MultiMeta } from './utils'
|
import { Meta, MultiMeta } from './meta'
|
||||||
|
|
||||||
export function ResolvedOpenGraphMetadata({
|
export function ResolvedOpenGraphMetadata({
|
||||||
openGraph,
|
openGraph,
|
||||||
|
|
11
packages/next/src/lib/metadata/generate/utils.ts
Normal file
11
packages/next/src/lib/metadata/generate/utils.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
export function resolveAsArrayOrUndefined<T = any>(
|
||||||
|
value: T | T[] | undefined | null
|
||||||
|
): undefined | T[] {
|
||||||
|
if (typeof value === 'undefined' || value === null) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return [value]
|
||||||
|
}
|
|
@ -1,19 +1,23 @@
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
import type { ResolvedMetadata } from './types/metadata-interface'
|
import type { ResolvedMetadata } from './types/metadata-interface'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
import { ResolvedBasicMetadata } from './generate/basic'
|
import { ResolvedBasicMetadata } from './generate/basic'
|
||||||
import { ResolvedAlternatesMetadata } from './generate/alternate'
|
import { ResolvedAlternatesMetadata } from './generate/alternate'
|
||||||
import { ResolvedOpenGraphMetadata } from './generate/opengraph'
|
import { ResolvedOpenGraphMetadata } from './generate/opengraph'
|
||||||
import { resolveMetadata } from './resolve-metadata'
|
import { resolveMetadata } from './resolve-metadata'
|
||||||
|
import { ResolvedIconsMetadata } from './generate/icons'
|
||||||
|
|
||||||
// Generate the actual React elements from the resolved metadata.
|
// Generate the actual React elements from the resolved metadata.
|
||||||
export async function Metadata({ metadata }: { metadata: any }) {
|
export async function Metadata({ metadata }: { metadata: any }) {
|
||||||
|
if (!metadata) return null
|
||||||
|
|
||||||
const resolved: ResolvedMetadata = await resolveMetadata(metadata)
|
const resolved: ResolvedMetadata = await resolveMetadata(metadata)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ResolvedBasicMetadata metadata={resolved} />
|
<ResolvedBasicMetadata metadata={resolved} />
|
||||||
<ResolvedAlternatesMetadata metadata={resolved} />
|
<ResolvedAlternatesMetadata metadata={resolved} />
|
||||||
<ResolvedOpenGraphMetadata openGraph={resolved.openGraph} />
|
<ResolvedOpenGraphMetadata openGraph={resolved.openGraph} />
|
||||||
|
<ResolvedIconsMetadata icons={resolved.icons} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -5,10 +5,16 @@ import type {
|
||||||
} from './types/metadata-interface'
|
} from './types/metadata-interface'
|
||||||
import type { Viewport } from './types/extra-types'
|
import type { Viewport } from './types/extra-types'
|
||||||
import type { ResolvedTwitterMetadata } from './types/twitter-types'
|
import type { ResolvedTwitterMetadata } from './types/twitter-types'
|
||||||
import type { AbsoluteTemplateString } from './types/metadata-types'
|
import type {
|
||||||
|
AbsoluteTemplateString,
|
||||||
|
Icon,
|
||||||
|
IconDescriptor,
|
||||||
|
Icons,
|
||||||
|
} from './types/metadata-types'
|
||||||
import { createDefaultMetadata } from './default-metadata'
|
import { createDefaultMetadata } from './default-metadata'
|
||||||
import { resolveOpenGraph } from './resolve-opengraph'
|
import { resolveOpenGraph } from './resolve-opengraph'
|
||||||
import { mergeTitle } from './resolve-title'
|
import { mergeTitle } from './resolve-title'
|
||||||
|
import { resolveAsArrayOrUndefined } from './generate/utils'
|
||||||
|
|
||||||
const viewPortKeys = {
|
const viewPortKeys = {
|
||||||
width: 'width',
|
width: 'width',
|
||||||
|
@ -48,6 +54,57 @@ type Item =
|
||||||
path?: string
|
path?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveViewport(
|
||||||
|
viewport: Metadata['viewport']
|
||||||
|
): ResolvedMetadata['viewport'] {
|
||||||
|
let resolved: ResolvedMetadata['viewport'] = null
|
||||||
|
|
||||||
|
if (typeof viewport === 'string') {
|
||||||
|
resolved = viewport
|
||||||
|
} else if (viewport) {
|
||||||
|
resolved = ''
|
||||||
|
for (const viewportKey_ in viewPortKeys) {
|
||||||
|
const viewportKey = viewportKey_ as keyof Viewport
|
||||||
|
if (viewport[viewportKey]) {
|
||||||
|
if (resolved) resolved += ', '
|
||||||
|
resolved += `${viewPortKeys[viewportKey]}=${viewport[viewportKey]}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resolved
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUrlIcon(icon: any): icon is string | URL {
|
||||||
|
return typeof icon === 'string' || icon instanceof URL
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveIcon(icon: Icon): IconDescriptor {
|
||||||
|
if (isUrlIcon(icon)) return { url: icon }
|
||||||
|
else if (Array.isArray(icon)) return icon
|
||||||
|
return icon
|
||||||
|
}
|
||||||
|
|
||||||
|
const IconKeys = ['icon', 'shortcut', 'apple', 'other'] as (keyof Icons)[]
|
||||||
|
|
||||||
|
function resolveIcons(icons: Metadata['icons']): ResolvedMetadata['icons'] {
|
||||||
|
if (!icons) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolved: ResolvedMetadata['icons'] = {}
|
||||||
|
if (Array.isArray(icons)) {
|
||||||
|
resolved.icon = icons.map(resolveIcon).filter(Boolean)
|
||||||
|
} else if (isUrlIcon(icons)) {
|
||||||
|
resolved.icon = [resolveIcon(icons)]
|
||||||
|
} else {
|
||||||
|
for (const key of IconKeys) {
|
||||||
|
const values = resolveAsArrayOrUndefined(icons[key])
|
||||||
|
if (values) resolved[key] = values.map(resolveIcon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resolved
|
||||||
|
}
|
||||||
|
|
||||||
// Merge the source metadata into the resolved target metadata.
|
// Merge the source metadata into the resolved target metadata.
|
||||||
function merge(
|
function merge(
|
||||||
target: ResolvedMetadata,
|
target: ResolvedMetadata,
|
||||||
|
@ -94,21 +151,11 @@ function merge(
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'viewport': {
|
case 'viewport': {
|
||||||
let content: string | null = null
|
target.viewport = resolveViewport(source.viewport)
|
||||||
const { viewport } = source
|
break
|
||||||
if (typeof viewport === 'string') {
|
}
|
||||||
content = viewport
|
case 'icons': {
|
||||||
} else if (viewport) {
|
target.icons = resolveIcons(source.icons)
|
||||||
content = ''
|
|
||||||
for (const viewportKey_ in viewPortKeys) {
|
|
||||||
const viewportKey = viewportKey_ as keyof Viewport
|
|
||||||
if (viewport[viewportKey]) {
|
|
||||||
if (content) content += ', '
|
|
||||||
content += `${viewPortKeys[viewportKey]}=${viewport[viewportKey]}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
target.viewport = content
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import type {
|
||||||
OpenGraph,
|
OpenGraph,
|
||||||
ResolvedOpenGraph,
|
ResolvedOpenGraph,
|
||||||
} from './types/opengraph-types'
|
} from './types/opengraph-types'
|
||||||
|
import { resolveAsArrayOrUndefined } from './generate/utils'
|
||||||
|
|
||||||
const OgTypFields = {
|
const OgTypFields = {
|
||||||
article: ['authors', 'tags'],
|
article: ['authors', 'tags'],
|
||||||
|
@ -22,16 +23,6 @@ const OgTypFields = {
|
||||||
],
|
],
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
function resolveAsArrayOrUndefined<T = any>(value: T): undefined | any[] {
|
|
||||||
if (typeof value === 'undefined' || value === null) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return [value]
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFieldsByOgType(ogType: OpenGraphType | undefined) {
|
function getFieldsByOgType(ogType: OpenGraphType | undefined) {
|
||||||
switch (ogType) {
|
switch (ogType) {
|
||||||
case 'article':
|
case 'article':
|
||||||
|
|
|
@ -15,7 +15,9 @@ import type {
|
||||||
ColorSchemeEnum,
|
ColorSchemeEnum,
|
||||||
Icon,
|
Icon,
|
||||||
Icons,
|
Icons,
|
||||||
|
IconURL,
|
||||||
ReferrerEnum,
|
ReferrerEnum,
|
||||||
|
ResolvedIcons,
|
||||||
Robots,
|
Robots,
|
||||||
TemplateString,
|
TemplateString,
|
||||||
Verification,
|
Verification,
|
||||||
|
@ -56,7 +58,7 @@ export interface Metadata {
|
||||||
|
|
||||||
// Defaults to rel="icon" but the Icons type can be used
|
// Defaults to rel="icon" but the Icons type can be used
|
||||||
// to get more specific about rel types
|
// to get more specific about rel types
|
||||||
icons?: null | Array<Icon> | Icons
|
icons?: null | IconURL | Array<Icon> | Icons
|
||||||
|
|
||||||
openGraph?: null | OpenGraph
|
openGraph?: null | OpenGraph
|
||||||
|
|
||||||
|
@ -145,7 +147,7 @@ export interface ResolvedMetadata {
|
||||||
|
|
||||||
// Defaults to rel="icon" but the Icons type can be used
|
// Defaults to rel="icon" but the Icons type can be used
|
||||||
// to get more specific about rel types
|
// to get more specific about rel types
|
||||||
icons: null | Icons
|
icons: null | ResolvedIcons
|
||||||
|
|
||||||
openGraph: null | ResolvedOpenGraph
|
openGraph: null | ResolvedOpenGraph
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,8 @@ export type Robots = {
|
||||||
googleBot?: string | Robots
|
googleBot?: string | Robots
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Icon = string | IconDescriptor | URL
|
export type IconURL = string | URL
|
||||||
|
export type Icon = IconURL | IconDescriptor
|
||||||
export type IconDescriptor = {
|
export type IconDescriptor = {
|
||||||
url: string | URL
|
url: string | URL
|
||||||
type?: string
|
type?: string
|
||||||
|
@ -74,20 +75,27 @@ export type IconDescriptor = {
|
||||||
}
|
}
|
||||||
export type Icons = {
|
export type Icons = {
|
||||||
// rel="icon"
|
// rel="icon"
|
||||||
icon?: Icon | Array<Icon>
|
icon?: Icon | Icon[]
|
||||||
// rel="shortcut icon"
|
// rel="shortcut icon"
|
||||||
shortcut?: Icon | Array<Icon>
|
shortcut?: Icon | Icon[]
|
||||||
// rel="apple-touch-icon"
|
// rel="apple-touch-icon"
|
||||||
apple?: Icon | Array<Icon>
|
apple?: Icon | Icon[]
|
||||||
// rel inferred from descriptor, defaults to "icon"
|
// rel inferred from descriptor, defaults to "icon"
|
||||||
other?: Icon | Array<Icon>
|
other?: IconDescriptor | IconDescriptor[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Verification = {
|
export type Verification = {
|
||||||
google?: null | string | number | Array<string | number>
|
google?: null | string | number | (string | number)[]
|
||||||
yahoo?: null | string | number | Array<string | number>
|
yahoo?: null | string | number | (string | number)[]
|
||||||
// if you ad-hoc additional verification
|
// if you ad-hoc additional verification
|
||||||
other?: {
|
other?: {
|
||||||
[name: string]: string | number | Array<string | number>
|
[name: string]: string | number | (string | number)[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ResolvedIcons = {
|
||||||
|
icon?: IconDescriptor[]
|
||||||
|
shortcut?: IconDescriptor[]
|
||||||
|
apple?: IconDescriptor[]
|
||||||
|
other?: IconDescriptor[]
|
||||||
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ import {
|
||||||
} from '../client/components/app-router-headers'
|
} from '../client/components/app-router-headers'
|
||||||
import type { StaticGenerationAsyncStorage } from '../client/components/static-generation-async-storage'
|
import type { StaticGenerationAsyncStorage } from '../client/components/static-generation-async-storage'
|
||||||
import { formatServerError } from '../lib/format-server-error'
|
import { formatServerError } from '../lib/format-server-error'
|
||||||
import { Metadata } from '../lib/metadata/ui'
|
import { Metadata } from '../lib/metadata/metadata'
|
||||||
import type { RequestAsyncStorage } from '../client/components/request-async-storage'
|
import type { RequestAsyncStorage } from '../client/components/request-async-storage'
|
||||||
import { runWithRequestAsyncStorage } from './run-with-request-async-storage'
|
import { runWithRequestAsyncStorage } from './run-with-request-async-storage'
|
||||||
import { runWithStaticGenerationAsyncStorage } from './run-with-static-generation-async-storage'
|
import { runWithStaticGenerationAsyncStorage } from './run-with-static-generation-async-storage'
|
||||||
|
|
20
test/e2e/app-dir/metadata/app/icons/descriptor/page.js
Normal file
20
test/e2e/app-dir/metadata/app/icons/descriptor/page.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
export default function page() {
|
||||||
|
return 'icons'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
icons: {
|
||||||
|
icon: [{ url: '/icon.png' }, new URL('/icon.png', 'https://example.com')],
|
||||||
|
shortcut: ['/shortcut-icon.png'],
|
||||||
|
apple: [
|
||||||
|
{ url: '/apple-icon.png' },
|
||||||
|
{ url: '/apple-icon-x3.png', sizes: '180x180', type: 'image/png' },
|
||||||
|
],
|
||||||
|
other: [
|
||||||
|
{
|
||||||
|
rel: 'apple-touch-icon-precomposed',
|
||||||
|
url: '/apple-touch-icon-precomposed.png',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
15
test/e2e/app-dir/metadata/app/icons/page.js
Normal file
15
test/e2e/app-dir/metadata/app/icons/page.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
export default function page() {
|
||||||
|
return 'icons'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
icons: {
|
||||||
|
icon: '/icon.png',
|
||||||
|
shortcut: '/shortcut-icon.png',
|
||||||
|
apple: '/apple-icon.png',
|
||||||
|
other: {
|
||||||
|
rel: 'apple-touch-icon-precomposed',
|
||||||
|
url: '/apple-touch-icon-precomposed.png',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
7
test/e2e/app-dir/metadata/app/icons/string/page.js
Normal file
7
test/e2e/app-dir/metadata/app/icons/string/page.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export default function page() {
|
||||||
|
return 'icons'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
icons: '/icon.png',
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { createNextDescribe } from 'e2e-utils'
|
import { createNextDescribe } from 'e2e-utils'
|
||||||
|
import { BrowserInterface } from 'test/lib/browsers/base'
|
||||||
|
|
||||||
createNextDescribe(
|
createNextDescribe(
|
||||||
'app dir - metadata',
|
'app dir - metadata',
|
||||||
|
@ -11,21 +12,39 @@ createNextDescribe(
|
||||||
it('should skip for deploy currently', () => {})
|
it('should skip for deploy currently', () => {})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getTitle = (browser: BrowserInterface) =>
|
||||||
|
browser.elementByCss('title').text()
|
||||||
|
|
||||||
|
async function queryMetaProps(
|
||||||
|
browser: BrowserInterface,
|
||||||
|
tag: string,
|
||||||
|
query: string,
|
||||||
|
selectedKeys: string[]
|
||||||
|
) {
|
||||||
|
return await browser.eval(`
|
||||||
|
const res = {}
|
||||||
|
const el = document.querySelector('${tag}[${query}]')
|
||||||
|
for (const k of ${JSON.stringify(selectedKeys)}) {
|
||||||
|
res[k] = el?.getAttribute(k)
|
||||||
|
}
|
||||||
|
res`)
|
||||||
|
}
|
||||||
|
|
||||||
async function checkMeta(
|
async function checkMeta(
|
||||||
browser,
|
browser: BrowserInterface,
|
||||||
name,
|
name: string,
|
||||||
content,
|
content: string | string[],
|
||||||
property = 'property',
|
property: string = 'property',
|
||||||
tag = 'meta',
|
tag: string = 'meta',
|
||||||
field = 'content'
|
field: string = 'content'
|
||||||
) {
|
) {
|
||||||
const values = await browser.eval(
|
const values = await browser.eval(
|
||||||
`[...document.querySelectorAll('${tag}[${property}="${name}"]')].map((el) => el.${field})`
|
`[...document.querySelectorAll('${tag}[${property}="${name}"]')].map((el) => el.getAttribute("${field}"))`
|
||||||
)
|
)
|
||||||
if (Array.isArray(content)) {
|
if (Array.isArray(content)) {
|
||||||
expect(values).toEqual(content)
|
expect(values).toEqual(content)
|
||||||
} else {
|
} else {
|
||||||
console.log('expect', values[0], 'toContain', content)
|
|
||||||
expect(values[0]).toContain(content)
|
expect(values[0]).toContain(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,9 +169,7 @@ createNextDescribe(
|
||||||
it('should apply metadata when navigating client-side', async () => {
|
it('should apply metadata when navigating client-side', async () => {
|
||||||
const browser = await next.browser('/')
|
const browser = await next.browser('/')
|
||||||
|
|
||||||
const getTitle = () => browser.elementByCss('title').text()
|
expect(await getTitle(browser)).toBe('index page')
|
||||||
|
|
||||||
expect(await getTitle()).toBe('index page')
|
|
||||||
await browser
|
await browser
|
||||||
.elementByCss('#to-basic')
|
.elementByCss('#to-basic')
|
||||||
.click()
|
.click()
|
||||||
|
@ -165,12 +182,12 @@ createNextDescribe(
|
||||||
'name'
|
'name'
|
||||||
)
|
)
|
||||||
await browser.back().waitForElementByCss('#index', 2000)
|
await browser.back().waitForElementByCss('#index', 2000)
|
||||||
expect(await getTitle()).toBe('index page')
|
expect(await getTitle(browser)).toBe('index page')
|
||||||
await browser
|
await browser
|
||||||
.elementByCss('#to-title')
|
.elementByCss('#to-title')
|
||||||
.click()
|
.click()
|
||||||
.waitForElementByCss('#title', 2000)
|
.waitForElementByCss('#title', 2000)
|
||||||
expect(await getTitle()).toBe('this is the page title')
|
expect(await getTitle(browser)).toBe('this is the page title')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -209,6 +226,56 @@ createNextDescribe(
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('icons', () => {
|
||||||
|
const checkLink = (browser, name, content) =>
|
||||||
|
checkMeta(browser, name, content, 'rel', 'link', 'href')
|
||||||
|
|
||||||
|
it('should support basic object icons field', async () => {
|
||||||
|
const browser = await next.browser('/icons')
|
||||||
|
|
||||||
|
await checkLink(browser, 'shortcut icon', '/shortcut-icon.png')
|
||||||
|
await checkLink(browser, 'icon', '/icon.png')
|
||||||
|
await checkLink(browser, 'apple-touch-icon', '/apple-icon.png')
|
||||||
|
await checkLink(
|
||||||
|
browser,
|
||||||
|
'apple-touch-icon-precomposed',
|
||||||
|
'/apple-touch-icon-precomposed.png'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support basic string icons field', async () => {
|
||||||
|
const browser = await next.browser('/icons/string')
|
||||||
|
await checkLink(browser, 'icon', '/icon.png')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support basic complex descriptor icons field', async () => {
|
||||||
|
const browser = await next.browser('/icons/descriptor')
|
||||||
|
|
||||||
|
await checkLink(browser, 'shortcut icon', '/shortcut-icon.png')
|
||||||
|
await checkLink(browser, 'icon', [
|
||||||
|
'/icon.png',
|
||||||
|
'https://example.com/icon.png',
|
||||||
|
])
|
||||||
|
await checkLink(browser, 'apple-touch-icon', [
|
||||||
|
'/apple-icon.png',
|
||||||
|
'/apple-icon-x3.png',
|
||||||
|
])
|
||||||
|
|
||||||
|
await checkLink(
|
||||||
|
browser,
|
||||||
|
'apple-touch-icon-precomposed',
|
||||||
|
'/apple-touch-icon-precomposed.png'
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await queryMetaProps(browser, 'link', 'href="/apple-icon-x3.png"', [
|
||||||
|
'sizes',
|
||||||
|
'type',
|
||||||
|
])
|
||||||
|
).toEqual({ sizes: '180x180', type: 'image/png' })
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue