next.js/test/e2e/app-dir/metadata-streaming/metadata-streaming.test.ts
metadata-streaming.test.ts202 lines6.6 KB
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')
    })
  })
})
Quest for Codev2.0.0
/
SIGN IN