Back to Repositories

Testing Multi-Page Application Configuration and Building in Vue CLI

This test suite validates the multi-page functionality in Vue CLI, covering both development server and production build scenarios. It ensures proper page routing, asset management, and chunk splitting across multiple entry points.

Test Coverage Overview

The test suite provides comprehensive coverage of Vue CLI’s multi-page implementation:

  • Development server routing and page rendering
  • Production build asset generation and optimization
  • HTML template processing and customization
  • Chunk splitting and resource loading
  • Dynamic imports and lazy loading verification

Implementation Analysis

The testing approach utilizes Jest and Puppeteer to validate both build output and runtime behavior.

Key patterns include:
  • Project configuration setup with multiple entry points
  • Custom webpack chain configuration for optimization
  • Dynamic component imports for code splitting
  • Asset verification through file system checks

Technical Details

Testing tools and configuration:

  • Jest as the test runner
  • Puppeteer for browser automation
  • Vue CLI test utilities for project creation and serving
  • Custom port finding for test server
  • HTML content validation through DOM queries

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test environments for each scenario
  • Comprehensive asset verification
  • Runtime behavior validation
  • Resource cleanup after test completion
  • Modular test setup functions

vuejs/vue-cli

packages/@vue/cli-service/__tests__/multiPage.spec.js

            
jest.setTimeout(80000)

const path = require('path')
const portfinder = require('portfinder')
const createServer = require('@vue/cli-test-utils/createServer')
const { defaultPreset } = require('@vue/cli/lib/options')
const create = require('@vue/cli-test-utils/createTestProject')
const serve = require('@vue/cli-test-utils/serveWithPuppeteer')
const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer')

async function makeProjectMultiPage (project) {
  await project.write('vue.config.js', `
    module.exports = {
      pages: {
        index: { entry: 'src/main.js' },
        foo: { entry: 'src/foo.js' },
        bar: { entry: 'src/bar.js' },
        foobar: { entry: ['src/foobar.js'] },
        baz: {
          entry: 'src/main.js',
          template: 'public/baz.html',
          filename: 'qux.html'
        }
      },
      chainWebpack: config => {
        const splitOptions = config.optimization.get('splitChunks')
        config.optimization.splitChunks(Object.assign({}, splitOptions, {
          minSize: 100
        }))
      }
    }
  `)
  await project.write('public/baz.html', await project.read('public/index.html'))
  await project.write('src/foo.js', `
    import Vue from 'vue'
    new Vue({
      el: '#app',
      render: h => h('h1', 'Foo')
    })
  `)
  await project.write('src/bar.js', `
    import Vue from 'vue'
    import App from './App.vue'
    new Vue({
      el: '#app',
      render: h => h(App)
    })
  `)
  await project.write('src/foobar.js', `
    import Vue from 'vue'
    new Vue({
      el: '#app',
      render: h => h('h1', 'FooBar')
    })
  `)
  const app = await project.read('src/App.vue')
  await project.write('src/App.vue', app.replace(
    `import HelloWorld from './components/HelloWorld.vue'`,
    `const HelloWorld = () => import('./components/HelloWorld.vue')`
  ))
}

test('serve w/ multi page', async () => {
  const project = await create('e2e-multi-page-serve', defaultPreset)

  await makeProjectMultiPage(project)

  await serve(
    () => project.run('vue-cli-service serve'),
    async ({ page, url, helpers }) => {
      expect(await helpers.getText('h1')).toMatch(`Welcome to Your Vue.js App`)

      await page.goto(`${url}foo.html`)
      expect(await helpers.getText('h1')).toMatch(`Foo`)

      await page.goto(`${url}bar.html`)
      expect(await helpers.getText('h1')).toMatch(`Welcome to Your Vue.js App`)

      await page.goto(`${url}foo`)
      expect(await helpers.getText('h1')).toMatch(`Foo`)

      await page.goto(`${url}bar`)
      expect(await helpers.getText('h1')).toMatch(`Welcome to Your Vue.js App`)

      await page.goto(`${url}foobar`)
      expect(await helpers.getText('h1')).toMatch(`FooBar`)
    }
  )
})

let server, browser
test('build w/ multi page', async () => {
  const project = await create('e2e-multi-page-build', defaultPreset)

  await makeProjectMultiPage(project)

  const { stdout } = await project.run('vue-cli-service build')
  expect(stdout).toMatch('Build complete.')

  // should generate the HTML pages
  expect(project.has('dist/index.html')).toBe(true)
  expect(project.has('dist/foo.html')).toBe(true)
  expect(project.has('dist/bar.html')).toBe(true)

  // should properly ignore the template file
  expect(project.has('dist/baz.html')).toBe(false)
  // should respect the `filename` field in a multi-page config
  expect(project.has('dist/qux.html')).toBe(true)

  const assertSharedAssets = file => {
    // should split and preload vendor chunk
    // expect(file).toMatch(/<link [^>]*js\/chunk-vendors[^>]*\.js" rel="preload" as="script">/)
    expect(file).toMatch(/<script [^>]*type="module" src="\/js\/chunk-vendors\.\w+\.js">/)
  }

  const index = await project.read('dist/index.html')
  assertSharedAssets(index)
  // should split and preload common js and css
  // expect(index).toMatch(/<link [^>]*js\/chunk-common[^>]*\.js" rel="preload" as="script">/)
  expect(index).toMatch(/<script [^>]*type="module" src="\/js\/chunk-common\.\w+\.js">/)
  expect(index).toMatch(/<link href="\/css\/chunk-common\.\w+\.css" rel="stylesheet">/)
  // expect(index).toMatch(/<link [^>]*chunk-common[^>]*\.css" rel="preload" as="style">/)
  // should preload correct page file
  // expect(index).toMatch(/<link [^>]*js\/index[^>]*\.js" rel="preload" as="script">/)
  // expect(index).not.toMatch(/<link [^>]*js\/foo[^>]*\.js" rel="preload" as="script">/)
  // expect(index).not.toMatch(/<link [^>]*js\/bar[^>]*\.js" rel="preload" as="script">/)
  // should prefetch async chunk js and css
  // expect(index).toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css" rel="prefetch">/)
  // expect(index).toMatch(/<link [^>]*js\/chunk-\w+\.\w+\.js" rel="prefetch">/)
  // should load correct page js
  expect(index).toMatch(/<script [^>]*type="module" src="\/js\/index\.\w+\.js">/)
  expect(index).not.toMatch(/<script [^>]*type="module" src="\/js\/foo\.\w+\.js">/)
  expect(index).not.toMatch(/<script [^>]*type="module" src="\/js\/bar\.\w+\.js">/)

  const foo = await project.read('dist/foo.html')
  assertSharedAssets(foo)
  // should preload correct page file
  // expect(foo).not.toMatch(/<link [^>]*js\/index[^>]*\.js" rel="preload" as="script">/)
  // expect(foo).toMatch(/<link [^>]*js\/foo[^>]*\.js" rel="preload" as="script">/)
  // expect(foo).not.toMatch(/<link [^>]*js\/bar[^>]*\.js" rel="preload" as="script">/)
  // should not prefetch async chunk js and css because it's not used by
  // this entry
  // expect(foo).not.toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css" rel="prefetch">/)
  // expect(foo).not.toMatch(/<link [^>]*js\/chunk-\w+\.\w+\.js" rel="prefetch">/)
  // should load correct page js
  expect(foo).not.toMatch(/<script [^>]*type="module" src="\/js\/index\.\w+\.js">/)
  expect(foo).toMatch(/<script [^>]*type="module" src="\/js\/foo\.\w+\.js">/)
  expect(foo).not.toMatch(/<script [^>]*type="module" src="\/js\/bar\.\w+\.js">/)

  const bar = await project.read('dist/bar.html')
  assertSharedAssets(bar)
  // bar & index have a shared common chunk (App.vue)
  // expect(bar).toMatch(/<link [^>]*js\/chunk-common[^>]*\.js" rel="preload" as="script">/)
  // expect(bar).toMatch(/<script [^>]*src="\/js\/chunk-common\.\w+\.js">/)
  expect(bar).toMatch(/<link href="\/css\/chunk-common\.\w+\.css" rel="stylesheet">/)
  // expect(bar).toMatch(/<link [^>]*chunk-common[^>]*\.css" rel="preload" as="style">/)
  // should preload correct page file
  // expect(bar).not.toMatch(/<link [^>]*js\/index[^>]*\.js" rel="preload" as="script">/)
  // expect(bar).not.toMatch(/<link [^>]*js\/foo[^>]*\.js" rel="preload" as="script">/)
  // expect(bar).toMatch(/<link [^>]*js\/bar[^>]*\.js" rel="preload" as="script">/)
  // should prefetch async chunk js and css
  // expect(bar).toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css" rel="prefetch">/)
  // expect(bar).toMatch(/<link [^>]*js\/chunk-\w+\.\w+\.js" rel="prefetch">/)
  // should load correct page js
  expect(bar).not.toMatch(/<script [^>]*type="module" src="\/js\/index\.\w+\.js" >/)
  expect(bar).not.toMatch(/<script [^>]*type="module" src="\/js\/foo\.\w+\.js" >/)
  expect(bar).toMatch(/<script [^>]*type="module" src="\/js\/bar\.\w+\.js">/)

  // assert pages work
  const port = await portfinder.getPortPromise()
  server = createServer({ root: path.join(project.dir, 'dist') })

  await new Promise((resolve, reject) => {
    server.listen(port, err => {
      if (err) return reject(err)
      resolve()
    })
  })

  const url = `http://localhost:${port}/`
  const launched = await launchPuppeteer(url)
  browser = launched.browser
  const page = launched.page

  const getH1Text = async () => page.evaluate(() => {
    return document.querySelector('h1').textContent
  })

  expect(await getH1Text()).toMatch('Welcome to Your Vue.js App')

  await page.goto(`${url}foo.html`)
  expect(await getH1Text()).toMatch('Foo')

  await page.goto(`${url}bar.html`)
  expect(await getH1Text()).toMatch('Welcome to Your Vue.js App')
})

afterAll(async () => {
  if (browser) {
    await browser.close()
  }
  if (server) {
    server.close()
  }
})