import { nextTestSetup } from 'e2e-utils' import { retry, createMultiDomMatcher } from 'next-test-utils' describe('app-dir - metadata-streaming', () => { const { next } = nextTestSetup({ files: __dirname, }) it('should delay the metadata render to body', async () => { const $ = await next.render$('/') expect($('head title').length).toBe(0) expect($('body title').length).toBe(1) }) it('should still load viewport meta tags even if metadata is delayed', async () => { const $ = await next.render$('/slow') expect($('meta[name="viewport"]').attr('content')).toBe( 'width=device-width, initial-scale=1' ) expect($('meta[charset]').attr('charset')).toBe('utf-8') }) it('should render the metadata in the browser', async () => { const browser = await next.browser('/') await retry(async () => { expect(await browser.elementByCss('title').text()).toBe('index page') }) }) it('should load the initial html without slow metadata during navigation', async () => { // navigate from / to /slow, the metadata should be empty first, e.g. no title. // then the metadata should be loaded after few seconds. const browser = await next.browser('/') await browser.elementByCss('#to-slow').click() await retry(async () => { expect(await browser.elementByCss('title').text()).toBe('slow page') const matchMultiDom = createMultiDomMatcher(browser) await matchMultiDom('meta', 'name', 'content', { description: 'slow page description', generator: 'next.js', 'application-name': 'test', referrer: 'origin-when-cross-origin', keywords: 'next.js,react,javascript', author: ['huozhi'], viewport: 'width=device-width, initial-scale=1', creator: 'huozhi', publisher: 'vercel', robots: 'index, follow', }) }) }) it('should send the blocking response for html limited bots', async () => { const $ = await next.render$( '/', undefined, // no query { headers: { 'user-agent': 'Twitterbot', }, } ) expect(await $('title').text()).toBe('index page') }) it('should send streaming response for headless browser bots', async () => { const browser = await next.browser('/') await retry(async () => { expect(await browser.elementByCss('title').text()).toBe('index page') }) }) it('should only insert metadata once into head or body', async () => { const browser = await next.browser('/slow') // each metadata should be inserted only once expect(await browser.hasElementByCssSelector('head title')).toBe(false) // only charset and viewport are rendered in head expect((await browser.elementsByCss('head meta')).length).toBe(2) expect((await browser.elementsByCss('body title')).length).toBe(1) // all metadata should be rendered in body expect((await browser.elementsByCss('body meta')).length).toBe(9) }) describe('dynamic api', () => { it('should render metadata to body', async () => { const $ = await next.render$('/dynamic-api') expect($('head title').length).toBe(0) expect($('body title').length).toBe(1) }) it('should load the metadata in browser', async () => { const browser = await next.browser('/dynamic-api') await retry(async () => { expect( await browser.elementByCss('body title', { state: 'attached' }).text() ).toMatch(/Dynamic api \d+/) }) }) }) describe('navigation API', () => { it('should trigger not-found boundary when call notFound', async () => { const browser = await next.browser('/notfound') // Show 404 page await retry(async () => { expect(await browser.elementByCss('h1').text()).toBe('404') }) }) it('should trigger redirection when call redirect', async () => { const browser = await next.browser('/redirect') // Redirect to home page expect(await browser.elementByCss('p').text()).toBe('index page') }) it('should trigger custom not-found in the boundary', async () => { const browser = await next.browser('/notfound/boundary') expect(await browser.elementByCss('h1').text()).toBe('Custom Not Found') }) it('should not duplicate metadata with navigation API', async () => { const browser = await next.browser('/notfound/boundary') const titleTags = await browser.elementsByCss('title') expect(titleTags.length).toBe(1) }) it('should render blocking 404 response status when html limited bots access notFound', async () => { const { status } = await next.fetch('/notfound', { headers: { 'user-agent': 'Twitterbot', }, }) expect(status).toBe(404) }) it('should render blocking 307 response status when html limited bots access redirect', async () => { const { status } = await next.fetch('/redirect', { headers: { 'user-agent': 'Twitterbot', }, redirect: 'manual', }) expect(status).toBe(307) }) }) describe('static', () => { it('should render static metadata in the head', async () => { const $ = await next.render$('/static/full') // We can't ensure if it's inserted into head or body since it's a race condition, // where sometimes the metadata can be suspended. expect($('title').length).toBe(1) expect($('title').text()).toBe('static page') }) it('should determine dynamic metadata in build and render in the body', async () => { const $ = await next.render$('/static/partial') expect($('title').length).toBe(1) expect($('body title').text()).toBe('partial static page') }) it('should still render dynamic metadata in the head for html bots', async () => { const $ = await next.render$( '/static/partial', {}, { headers: { 'user-agent': 'Twitterbot', }, } ) expect($('title').length).toBe(1) expect($('head title').text()).toBe('partial static page') }) it('should still render blocking metadata for Google speed insights bot (special case)', async () => { const $ = await next.render$( '/static/partial', {}, { headers: { 'user-agent': 'UA Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4590.2 Mobile Safari/537.36 Chrome-Lighthouse', }, } ) expect($('title').length).toBe(1) expect($('head title').text()).toBe('partial static page') }) }) })