jest.mock('fs')

const fs = require('fs-extra')
const path = require('path')
const Generator = require('../lib/Generator')
const { logs } = require('@vue/cli-shared-utils')
const stringifyJS = require('../lib/util/stringifyJS')

// prepare template fixtures
const templateDir = path.resolve(__dirname, 'template')
fs.ensureDirSync(templateDir)
fs.writeFileSync(path.resolve(templateDir, 'foo.js'), 'foo(<%- options.n %>)')
fs.ensureDirSync(path.resolve(templateDir, 'bar'))
fs.writeFileSync(path.resolve(templateDir, 'bar/bar.js'), 'bar(<%- m %>)')
fs.writeFileSync(path.resolve(templateDir, 'bar/_bar.js'), '.bar(<%- m %>)')
fs.writeFileSync(path.resolve(templateDir, 'entry.js'), `
import foo from 'foo'

new Vue({
  p: p(),
  baz,
  render: h => h(App)
}).$mount('#app')
`.trim())
fs.writeFileSync(path.resolve(templateDir, 'empty-entry.js'), `;`)
fs.writeFileSync(path.resolve(templateDir, 'main.ts'), `const a: string = 'hello';`)
fs.writeFileSync(path.resolve(templateDir, 'hello.vue'), `
<template>
  <p>Hello, {{ msg }}</p>
</template>
<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>
`)

// replace stubs
fs.writeFileSync(path.resolve(templateDir, 'replace.js'), `
---
extend: '${path.resolve(templateDir, 'bar/bar.js')}'
replace: !!js/regexp /bar\\((.*)\\)/
---
baz($1)
`.trim())

fs.writeFileSync(path.resolve(templateDir, 'multi-replace-source.js'), `
foo(1)
bar(2)
`.trim())

fs.writeFileSync(path.resolve(templateDir, 'multi-replace.js'), `
---
extend: '${path.resolve(templateDir, 'multi-replace-source.js')}'
replace:
  - !!js/regexp /foo\\((.*)\\)/
  - !!js/regexp /bar\\((.*)\\)/
---
<%# REPLACE %>
baz($1)
<%# END_REPLACE %>

<%# REPLACE %>
qux($1)
<%# END_REPLACE %>
`.trim())

// dotfile stubs
fs.ensureDirSync(path.resolve(templateDir, '_vscode'))
fs.writeFileSync(path.resolve(templateDir, '_vscode/config.json'), `{}`)
fs.writeFileSync(path.resolve(templateDir, '_gitignore'), 'foo')

beforeEach(() => {
  logs.warn = []
})

test('api: extendPackage', async () => {
  const generator = new Generator('/', {
    pkg: {
      name: 'hello',
      list: [1],
      vue: {
        foo: 1,
        bar: 2,
        pluginOptions: {
          graphqlMock: true,
          apolloEngine: false
        }
      }
    },
    plugins: [{
      id: 'test',
      apply: api => {
        api.extendPackage({
          name: 'hello2',
          list: [2],
          vue: {
            foo: 2,
            baz: 3,
            pluginOptions: {
              enableInSFC: true
            }
          }
        })
      }
    }]
  })

  await generator.generate()

  const pkg = JSON.parse(fs.readFileSync('/package.json', 'utf-8'))
  expect(pkg).toEqual({
    name: 'hello2',
    list: [1, 2],
    vue: {
      foo: 2,
      bar: 2,
      baz: 3,
      pluginOptions: {
        graphqlMock: true,
        apolloEngine: false,
        enableInSFC: true
      }
    }
  })
})

test('api: extendPackage function', async () => {
  const generator = new Generator('/', {
    pkg: { foo: 1 },
    plugins: [{
      id: 'test',
      apply: api => {
        api.extendPackage(pkg => ({
          foo: pkg.foo + 1
        }))
      }
    }]
  })

  await generator.generate()

  const pkg = JSON.parse(fs.readFileSync('/package.json', 'utf-8'))
  expect(pkg).toEqual({
    foo: 2
  })
})

test('api: extendPackage allow git, github, http, file version ranges', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          api.extendPackage({
            dependencies: {
              foo: 'git+ssh://git@github.com:npm/npm.git#v1.0.27',
              baz: 'git://github.com/npm/npm.git#v1.0.27',
              bar: 'expressjs/express',
              bad: 'mochajs/mocha#4727d357ea',
              bac: 'http://asdf.com/asdf.tar.gz',
              bae: 'file:../dyl',
              bcd: 'npm:vue@^3.0.0',
              'my-lib': 'https://bitbucket.org/user/my-lib.git#semver:^1.0.0'
            }
          })
        }
      }
    ]
  })

  await generator.generate()

  const pkg = JSON.parse(fs.readFileSync('/package.json', 'utf-8'))
  expect(pkg).toEqual({
    dependencies: {
      foo: 'git+ssh://git@github.com:npm/npm.git#v1.0.27',
      baz: 'git://github.com/npm/npm.git#v1.0.27',
      bar: 'expressjs/express',
      bad: 'mochajs/mocha#4727d357ea',
      bac: 'http://asdf.com/asdf.tar.gz',
      bae: 'file:../dyl',
      bcd: 'npm:vue@^3.0.0',
      'my-lib': 'https://bitbucket.org/user/my-lib.git#semver:^1.0.0'
    }
  })
})

test('api: extendPackage merge nonstrictly semver deps', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          api.extendPackage({
            dependencies: {
              'my-lib': 'https://bitbucket.org/user/my-lib.git#semver:1.0.0',
              bar: 'expressjs/express'
            }
          })
        }
      },
      {
        id: 'test2',
        apply: api => {
          api.extendPackage({
            dependencies: {
              'my-lib': 'https://bitbucket.org/user/my-lib.git#semver:1.2.0',
              bar: 'expressjs/express'
            }
          })
        }
      }
    ]
  })

  await generator.generate()

  const pkg = JSON.parse(fs.readFileSync('/package.json', 'utf-8'))
  expect(pkg).toEqual({
    dependencies: {
      'my-lib': 'https://bitbucket.org/user/my-lib.git#semver:1.2.0',
      bar: 'expressjs/express'
    }
  })
})

test('api: extendPackage merge dependencies', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test1',
        apply: api => {
          api.extendPackage({
            dependencies: {
              foo: '^1.1.0',
              bar: '^1.0.0'
            }
          })
        }
      },
      {
        id: 'test2',
        apply: api => {
          api.extendPackage({
            dependencies: {
              foo: '^1.0.0',
              baz: '^1.0.0'
            }
          })
        }
      }
    ]
  })

  await generator.generate()

  const pkg = JSON.parse(fs.readFileSync('/package.json', 'utf-8'))
  expect(pkg).toEqual({
    dependencies: {
      foo: '^1.1.0',
      bar: '^1.0.0',
      baz: '^1.0.0'
    }
  })
})

test('api: warn invalid dep range', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test1',
        apply: api => {
          api.extendPackage({
            dependencies: {
              foo: 'foo'
            }
          })
        }
      }
    ]
  })

  await generator.generate()

  expect(logs.warn.some(([msg]) => {
    return (
      msg.match(/invalid version range for dependency "foo"/) &&
      msg.match(/injected by generator "test1"/)
    )
  })).toBe(true)
})

test('api: warn invalid dep range when non-string', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test1',
        apply: api => {
          api.extendPackage({
            dependencies: {
              foo: null
            }
          })
        }
      }
    ]
  })

  await generator.generate()

  expect(logs.warn.some(([msg]) => {
    return (
      msg.match(/invalid version range for dependency "foo"/) &&
      msg.match(/injected by generator "test1"/)
    )
  })).toBe(true)
})

test('api: extendPackage dependencies conflict', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test1',
        apply: api => {
          api.extendPackage({
            dependencies: {
              foo: '^1.0.0'
            }
          })
        }
      },
      {
        id: 'test2',
        apply: api => {
          api.extendPackage({
            dependencies: {
              foo: '^2.0.0'
            }
          })
        }
      }
    ]
  })

  await generator.generate()

  expect(logs.warn.some(([msg]) => {
    return (
      msg.match(/conflicting versions for project dependency "foo"/) &&
      msg.match(/\^1\.0\.0 injected by generator "test1"/) &&
      msg.match(/\^2\.0\.0 injected by generator "test2"/) &&
      msg.match(/Using newer version \(\^2\.0\.0\)/)
    )
  })).toBe(true)
})

test('api: extendPackage merge warn nonstrictly semver deps', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test3',
        apply: api => {
          api.extendPackage({
            dependencies: {
              bar: 'expressjs/express'
            }
          })
        }
      },
      {
        id: 'test4',
        apply: api => {
          api.extendPackage({
            dependencies: {
              bar: 'expressjs/express#1234'
            }
          })
        }
      }
    ]
  })

  await generator.generate()

  expect(logs.warn.some(([msg]) => {
    return (
      msg.match(/conflicting versions for project dependency "bar"/) &&
      msg.match(/expressjs\/express injected by generator "test3"/) &&
      msg.match(/expressjs\/express#1234 injected by generator "test4"/) &&
      msg.match(/Using version \(expressjs\/express\)/)
    )
  })).toBe(true)
})

test('api: extendPackage + { merge: false }', async () => {
  const generator = new Generator('/', {
    pkg: {
      name: 'hello',
      list: [1],
      vue: {
        foo: 1,
        bar: 2
      }
    },
    plugins: [{
      id: 'test',
      apply: api => {
        api.extendPackage(
          {
            name: 'hello2',
            list: [2],
            vue: {
              foo: 2,
              baz: 3
            }
          },
          { merge: false }
        )
      }
    }]
  })

  await generator.generate()

  const pkg = JSON.parse(fs.readFileSync('/package.json', 'utf-8'))
  expect(pkg).toEqual({
    name: 'hello2',
    list: [2],
    vue: {
      foo: 2,
      baz: 3
    }
  })
})

test('api: extendPackage + { prune: true }', async () => {
  const generator = new Generator('/', {
    pkg: {
      name: 'hello',
      version: '0.0.0',
      dependencies: {
        foo: '1.0.0'
      },
      vue: {
        bar: 1,
        baz: 2
      }
    },
    plugins: [{
      id: 'test',
      apply: api => {
        api.extendPackage(
          {
            name: null,
            dependencies: {
              foo: null,
              qux: '2.0.0'
            },
            vue: {
              bar: null,
              baz: 3
            }
          },
          { prune: true }
        )
      }
    }]
  })

  await generator.generate()

  // should not warn about the null versions
  expect(logs.warn.length).toBe(0)

  const pkg = JSON.parse(fs.readFileSync('/package.json', 'utf-8'))
  expect(pkg).toEqual({
    version: '0.0.0',
    dependencies: {
      qux: '2.0.0'
    },
    vue: {
      baz: 3
    }
  })
})

test('api: extendPackage + { warnIncompatibleVersions: false }', async () => {
  const generator = new Generator('/', {
    pkg: {
      devDependencies: {
        eslint: '^4.0.0'
      }
    },
    plugins: [{
      id: 'test',
      apply: api => {
        api.extendPackage(
          {
            devDependencies: {
              eslint: '^6.0.0'
            }
          },
          { warnIncompatibleVersions: false }
        )
      }
    }]
  })

  await generator.generate()
  const pkg = JSON.parse(fs.readFileSync('/package.json', 'utf-8'))

  // should not warn about the version conflicts
  expect(logs.warn.length).toBe(0)
  // should use the newer version
  expect(pkg).toEqual({
    devDependencies: {
      eslint: '^6.0.0'
    }
  })
})

test('api: extendPackage + { forceOverwrite: true }', async () => {
  const generator = new Generator('/', {
    pkg: {
      devDependencies: {
        'sass-loader': '^11.0.0'
      }
    },
    plugins: [{
      id: 'test',
      apply: api => {
        api.extendPackage(
          {
            devDependencies: {
              'sass-loader': '^10.0.0'
            }
          },
          { warnIncompatibleVersions: false, forceOverwrite: true }
        )
      }
    }]
  })

  await generator.generate()
  const pkg = JSON.parse(fs.readFileSync('/package.json', 'utf-8'))

  // should not warn about the version conflicts
  expect(logs.warn.length).toBe(0)
  // should use the newer version
  expect(pkg).toEqual({
    devDependencies: {
      'sass-loader': '^10.0.0'
    }
  })
})

test('api: render fs directory', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test1',
        apply: api => {
          api.render('./template', { m: 2 })
        },
        options: {
          n: 1
        }
      }
    ]
  })

  await generator.generate()

  expect(fs.readFileSync('/foo.js', 'utf-8')).toMatch('foo(1)')
  expect(fs.readFileSync('/bar/bar.js', 'utf-8')).toMatch('bar(2)')
  expect(fs.readFileSync('/bar/.bar.js', 'utf-8')).toMatch('.bar(2)')
  expect(fs.readFileSync('/replace.js', 'utf-8')).toMatch('baz(2)')
  expect(fs.readFileSync('/multi-replace.js', 'utf-8')).toMatch('baz(1)\nqux(2)')
  expect(fs.readFileSync('/.gitignore', 'utf-8')).toMatch('foo')
  expect(fs.readFileSync('/.vscode/config.json', 'utf-8')).toMatch('{}')
})

// #4774
test('api: call render inside an anonymous function', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test1',
        apply: api => {
          (() => {
            api.render('./template', { m: 2 })
          })()
        },
        options: {
          n: 1
        }
      }
    ]
  })

  await generator.generate()
  expect(fs.readFileSync('/foo.js', 'utf-8')).toMatch('foo(1)')
})

test('api: render object', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test1',
        apply: api => {
          api.render({
            'foo1.js': path.join(templateDir, 'foo.js'),
            'bar/bar1.js': path.join(templateDir, 'bar/bar.js')
          }, { m: 3 })
        },
        options: {
          n: 2
        }
      }
    ]
  })

  await generator.generate()

  expect(fs.readFileSync('/foo1.js', 'utf-8')).toMatch('foo(2)')
  expect(fs.readFileSync('/bar/bar1.js', 'utf-8')).toMatch('bar(3)')
})

test('api: render middleware', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test1',
        apply: (api, options) => {
          api.render((files, render) => {
            files['foo2.js'] = render('foo(<%- n %>)', options)
            files['bar/bar2.js'] = render('bar(<%- n %>)', options)
          })
        },
        options: {
          n: 3
        }
      }
    ]
  })

  await generator.generate()

  expect(fs.readFileSync('/foo2.js', 'utf-8')).toMatch('foo(3)')
  expect(fs.readFileSync('/bar/bar2.js', 'utf-8')).toMatch('bar(3)')
})

test('api: hasPlugin', () => {
  // eslint-disable-next-line no-new
  new Generator('/', {
    plugins: [
      {
        id: 'foo',
        apply: api => {
          expect(api.hasPlugin('foo')).toBe(true)
          expect(api.hasPlugin('bar')).toBe(true)
          expect(api.hasPlugin('baz')).toBe(true)
          expect(api.hasPlugin('vue-cli-plugin-bar')).toBe(true)
          expect(api.hasPlugin('@vue/cli-plugin-baz')).toBe(true)
        }
      },
      {
        id: 'vue-cli-plugin-bar',
        apply: () => {}
      },
      {
        id: '@vue/cli-plugin-baz',
        apply: () => {}
      }
    ]
  })
})

test('api: onCreateComplete', async () => {
  const fn = () => {}
  const cbs = []
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          api.onCreateComplete(fn)
        }
      }
    ],
    afterInvokeCbs: cbs
  })

  await generator.generate()

  expect(cbs).toContain(fn)
})

test('api: afterInvoke', async () => {
  const fn = () => {}
  const cbs = []
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          api.afterInvoke(fn)
        }
      }
    ],
    afterInvokeCbs: cbs
  })

  await generator.generate()

  expect(cbs).toContain(fn)
})

test('api: afterAnyInvoke and afterInvoke in hooks', async () => {
  const fooAnyInvokeHandler = () => {}
  const fooInvokeHandler = () => {}
  const barAnyInvokeHandler = () => {}
  const barInvokeHandler = () => {}

  const getGeneratorFn = (invokeHandler, anyInvokeHandler) => {
    const generatorFn = () => {}
    generatorFn.hooks = api => {
      api.afterInvoke(invokeHandler)
      api.afterAnyInvoke(anyInvokeHandler)
    }
    return generatorFn
  }

  jest.doMock('vue-cli-plugin-foo-hooks/generator', () => {
    return getGeneratorFn(fooInvokeHandler, fooAnyInvokeHandler)
  }, { virtual: true })

  jest.doMock('vue-cli-plugin-bar-hooks/generator', () => {
    return getGeneratorFn(barInvokeHandler, barAnyInvokeHandler)
  }, { virtual: true })

  const afterAnyInvokeCbs = []
  const afterInvokeCbs = []
  const generator = new Generator('/', {
    pkg: {
      devDependencies: {
        'vue-cli-plugin-foo-hooks': '1.0.0',
        'vue-cli-plugin-bar-hooks': '1.0.0'
      }
    },
    plugins: [
      {
        id: 'vue-cli-plugin-foo-hooks',
        apply: getGeneratorFn(fooInvokeHandler, fooAnyInvokeHandler)
      }
    ],
    afterInvokeCbs,
    afterAnyInvokeCbs
  })

  await generator.generate()

  expect(afterAnyInvokeCbs).toEqual([fooAnyInvokeHandler, barAnyInvokeHandler])
  expect(afterInvokeCbs).toEqual([fooInvokeHandler])
})

test('api: resolve', () => {
  // eslint-disable-next-line no-new
  new Generator('/foo/bar', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          expect(api.resolve('baz')).toBe(path.resolve('/foo/bar', 'baz'))
        }
      }
    ]
  })
})

test('api: addEntryImport & addEntryInjection', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          api.injectImports('main.js', `import bar from 'bar'`)
          api.injectRootOptions('main.js', ['foo', 'bar'])
          api.render({
            'main.js': path.join(templateDir, 'entry.js')
          })
        }
      }
    ]
  })

  await generator.generate()
  expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/import foo from 'foo'\r?\nimport bar from 'bar'/)
  expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/new Vue\({\s+p: p\(\),\s+baz,\s+foo,\s+bar,\s+render: h => h\(App\)\s+}\)/)
})

test('api: injectImports to empty file', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          api.injectImports('main.js', `import foo from 'foo'`)
          api.injectImports('main.js', `import bar from 'bar'`)
          api.render({
            'main.js': path.join(templateDir, 'empty-entry.js')
          })
        }
      }
    ]
  })

  await generator.generate()
  expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/import foo from 'foo'\r?\nimport bar from 'bar'/)
})

test('api: injectImports to typescript file', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          api.injectImports('main.ts', `import foo from 'foo'`)
          api.render({
            'main.ts': path.join(templateDir, 'main.ts')
          })
        }
      }
    ]
  })

  await generator.generate()
  expect(fs.readFileSync('/main.ts', 'utf-8')).toMatch(/import foo from 'foo'/)
})

test('api: addEntryDuplicateImport', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          api.injectImports('main.js', `import foo from 'foo'`)
          api.render({
            'main.js': path.join(templateDir, 'entry.js')
          })
        }
      }
    ]
  })

  await generator.generate()
  expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/^import foo from 'foo'\s+new Vue/)
})

test('api: injectImport for .vue files', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          api.injectImports('hello.vue', `import foo from 'foo'`)
          api.render({
            'hello.vue': path.join(templateDir, 'hello.vue')
          })
        }
      }
    ]
  })

  await generator.generate()
  const content = fs.readFileSync('/hello.vue', 'utf-8')
  expect(content).toMatch(/import foo from 'foo'/)
  expect(content).toMatch(/<template>([\s\S]*)<\/template>/)
})

test('api: addEntryDuplicateInjection', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          api.injectRootOptions('main.js', 'baz')
          api.render({
            'main.js': path.join(templateDir, 'entry.js')
          })
        }
      }
    ]
  })

  await generator.generate()
  expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/{\s+p: p\(\),\s+baz,\s+render/)
})

test('api: addEntryDuplicateNonIdentifierInjection', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          api.injectRootOptions('main.js', 'p: p()')
          api.render({
            'main.js': path.join(templateDir, 'entry.js')
          })
        }
      }
    ]
  })

  await generator.generate()
  expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/{\s+p: p\(\),\s+baz,\s+render/)
})

test('api: addConfigTransform', async () => {
  const configs = {
    fooConfig: {
      bar: 42
    }
  }

  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          api.addConfigTransform('fooConfig', {
            file: {
              json: ['foo.config.json']
            }
          })
          api.extendPackage(configs)
        }
      }
    ]
  })

  await generator.generate({
    extractConfigFiles: true
  })

  const json = v => JSON.stringify(v, null, 2)
  expect(fs.readFileSync('/foo.config.json', 'utf-8')).toMatch(json(configs.fooConfig))
  expect(generator.pkg).not.toHaveProperty('fooConfig')
})

test('api: addConfigTransform (multiple)', async () => {
  const configs = {
    bazConfig: {
      field: 2501
    }
  }

  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          api.addConfigTransform('bazConfig', {
            file: {
              js: ['.bazrc.js'],
              json: ['.bazrc', 'baz.config.json']
            }
          })
          api.extendPackage(configs)
        }
      }
    ]
  })

  await generator.generate({
    extractConfigFiles: true
  })

  const js = v => `module.exports = ${stringifyJS(v, null, 2)}`
  expect(fs.readFileSync('/.bazrc.js', 'utf-8')).toMatch(js(configs.bazConfig))
  expect(generator.pkg).not.toHaveProperty('bazConfig')
})

test('api: addConfigTransform transform vue warn', async () => {
  const configs = {
    vue: {
      lintOnSave: 'default'
    }
  }

  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          api.addConfigTransform('vue', {
            file: {
              js: ['vue.config.js']
            }
          })
          api.extendPackage(configs)
        }
      }
    ]
  })

  await generator.generate({
    extractConfigFiles: true
  })

  expect(fs.readFileSync('/vue.config.js', 'utf-8')).toMatch(`module.exports = {\n  lintOnSave: 'default'\n}`)
  expect(logs.warn.some(([msg]) => {
    return msg.match(/Reserved config transform 'vue'/)
  })).toBe(true)
})

test('avoid overwriting files that have not been modified', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test1',
        apply: (api, options) => {
          api.render((files, render) => {
            files['foo.js'] = render('foo()')
          })
        }
      }
    ],
    files: {
      // skip writing to this file
      'existFile.js': 'existFile()'
    }
  })

  await generator.generate()

  expect(fs.readFileSync('/foo.js', 'utf-8')).toMatch('foo()')
  expect(fs.existsSync('/existFile.js')).toBe(false)
})

test('overwrite files that have been modified', async () => {
  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test1',
        apply: (api, options) => {
          api.render((files, render) => {
            files['existFile.js'] = render('foo()')
          })
        }
      }
    ],
    files: {
      'existFile.js': 'existFile()'
    }
  })

  await generator.generate()

  expect(fs.readFileSync('/existFile.js', 'utf-8')).toMatch('foo()')
})

test('extract config files', async () => {
  const configs = {
    vue: {
      lintOnSave: false
    },
    babel: {
      presets: ['@vue/app']
    },
    postcss: {
      autoprefixer: {}
    },
    eslintConfig: {
      extends: ['plugin:vue/essential']
    },
    jest: {
      foo: 'bar'
    },
    browserslist: [
      '> 1%',
      'not <= IE8'
    ]
  }

  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          api.extendPackage(configs)
        }
      }
    ]
  })

  await generator.generate({
    extractConfigFiles: true
  })

  const js = v => `module.exports = ${stringifyJS(v, null, 2)}`
  expect(fs.readFileSync('/vue.config.js', 'utf-8')).toMatch(js(configs.vue))
  expect(fs.readFileSync('/babel.config.js', 'utf-8')).toMatch(js(configs.babel))
  expect(fs.readFileSync('/postcss.config.js', 'utf-8')).toMatch(js(configs.postcss))
  expect(fs.readFileSync('/.eslintrc.js', 'utf-8')).toMatch(js(configs.eslintConfig))
  expect(fs.readFileSync('/jest.config.js', 'utf-8')).toMatch(js(configs.jest))
  expect(fs.readFileSync('/.browserslistrc', 'utf-8')).toMatch('> 1%\nnot <= IE8')
})

test('generate a JS-Only value from a string', async () => {
  const jsAsString = 'true ? "alice" : "bob"'

  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          api.extendPackage({
            testScript: api.makeJSOnlyValue(jsAsString)
          })
        }
      }
    ]
  })

  await generator.generate({})

  expect(generator.pkg).toHaveProperty('testScript')
  expect(typeof generator.pkg.testScript).toBe('function')
})

test('run a codemod on the entry file', async () => {
  // A test codemod that tranforms `new Vue` to `new TestVue`
  const codemod = (fileInfo, api) => {
    const j = api.jscodeshift
    return j(fileInfo.source)
        .find(j.NewExpression, {
          callee: { name: 'Vue' },
          arguments: [{ type: 'ObjectExpression' }]
        })
        .replaceWith(({ node }) => {
          node.callee.name = 'TestVue'
          return node
        })
        .toSource()
  }

  const generator = new Generator('/', {
    plugins: [
      {
        id: 'test',
        apply: api => {
          api.render({
            'main.js': path.join(templateDir, 'entry.js')
          })

          api.transformScript('main.js', codemod)
        }
      }
    ]
  })

  await generator.generate()
  expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/new TestVue/)
})
