test.js 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219
  1. process.env.VUE_LOADER_TEST = true
  2. var path = require('path')
  3. var webpack = require('webpack')
  4. var MemoryFS = require('memory-fs')
  5. var jsdom = require('jsdom')
  6. var expect = require('chai').expect
  7. var rimraf = require('rimraf')
  8. var genId = require('vue-loader/lib/gen-id')
  9. var SourceMapConsumer = require('source-map').SourceMapConsumer
  10. var ExtractTextPlugin = require("extract-text-webpack-plugin")
  11. var compiler = require('vue-loader/lib/template-compiler')
  12. var normalizeNewline = require('normalize-newline')
  13. var vuxLoader = require('../src/index.js')
  14. var i18nParser = require('../libs/parse-i18n-function').parse
  15. const i18nParserForScript = require('../libs/replace-i18n-for-script').replace
  16. const getI18nBlock = require('../libs/get-i18n-block').get
  17. const getI18nBlockWithLocale = require('../libs/get-i18n-block').getWithLocale
  18. function getOptionsPlugin(config) {
  19. const match = config.plugins.filter(one => {
  20. return one.constructor.name === 'LoaderOptionsPlugin'
  21. })
  22. return match[0]
  23. }
  24. // var loaderPath = 'expose-loader?vueModule!' + path.resolve(__dirname, '../node_modules/vue-loader/index.js')
  25. var loaderPath = 'expose-loader?vueModule!' + path.resolve(__dirname, '../src/index.js') + '!vue-loader'
  26. var mfs = new MemoryFS()
  27. var globalConfig = {
  28. output: {
  29. path: '/',
  30. filename: 'test.build.js'
  31. },
  32. module: {
  33. rules: [
  34. {
  35. test: /\.vue$/,
  36. loader: loaderPath
  37. }
  38. ]
  39. }
  40. }
  41. function bundle(options, vuxOptions, cb) {
  42. var vueOptions = options.vue
  43. delete options.vue
  44. var config = Object.assign(globalConfig, options)
  45. // assign vue Options
  46. if (vueOptions) {
  47. config.plugins = (config.plugins || []).concat(new webpack.LoaderOptionsPlugin({
  48. vue: vueOptions
  49. }))
  50. }
  51. let basicVux = {
  52. options: {
  53. loaderString: loaderPath,
  54. rewriteLoaderString: false,
  55. isWebpack2: true,
  56. isTest: true
  57. }
  58. }
  59. if (vuxOptions.options) {
  60. for (let i in vuxOptions.options) {
  61. basicVux.options[i] = vuxOptions.options[i]
  62. }
  63. }
  64. if (vuxOptions.plugins) {
  65. basicVux.plugins = vuxOptions.plugins
  66. }
  67. config = vuxLoader.merge(config, basicVux)
  68. var webpackCompiler = webpack(config)
  69. webpackCompiler.outputFileSystem = mfs
  70. webpackCompiler.run(function (err, stats) {
  71. expect(err).to.be.null
  72. if (stats.compilation.errors.length) {
  73. stats.compilation.errors.forEach(function (err) {
  74. console.error(err.message)
  75. })
  76. }
  77. expect(stats.compilation.errors).to.be.empty
  78. cb(mfs.readFileSync('/test.build.js').toString())
  79. })
  80. }
  81. function test(options, vuxOptions, assert) {
  82. bundle(options, vuxOptions, function (code) {
  83. jsdom.env({
  84. html: '<!DOCTYPE html><html><head></head><body></body></html>',
  85. src: [code],
  86. done: function (err, window) {
  87. if (err) {
  88. console.log(err[0].data.error.stack)
  89. expect(err).to.be.null
  90. }
  91. assert(window, interopDefault(window.vueModule), window.vueModule)
  92. }
  93. })
  94. })
  95. }
  96. function mockRender(options, data) {
  97. return options.render.call(Object.assign({
  98. _v(val) {
  99. return val
  100. },
  101. _self: {},
  102. $createElement(tag, data, children) {
  103. if (Array.isArray(data)) {
  104. children = data
  105. data = null
  106. }
  107. return {
  108. tag: tag,
  109. data: data,
  110. children: children
  111. }
  112. },
  113. _m(index) {
  114. return options.staticRenderFns[index].call(this)
  115. },
  116. _s(str) {
  117. return String(str)
  118. }
  119. }, data))
  120. }
  121. function interopDefault(module) {
  122. return module ? module.__esModule ? module.default : module : module
  123. }
  124. var parse = require('../src/libs/import-parser')
  125. const str = parse(`<script>
  126. import {
  127. Group
  128. } from 'vux';
  129. `, function (opts) {
  130. // console.log(opts)
  131. })
  132. var themeParse = require('../src/libs/get-less-variables')
  133. var commomMapper = function (opts) {
  134. components = opts.components.map(function (one) {
  135. return one.newName
  136. })
  137. return `import { ${components.join(', ')} } from 'vux'`
  138. }
  139. var vuxMapper = function (opts) {
  140. let str = ''
  141. opts.components.forEach(function (one) {
  142. if (one.originalName === 'AlertPlugin') {
  143. str += `import ${one.newName} from 'vux/src/plugins/Alert'\n`
  144. } else if (one.originalName === 'ToastPlugin') {
  145. str += `import ${one.newName} from 'vux/src/plugins/Toast'\n`
  146. }
  147. })
  148. return str
  149. }
  150. describe('vux-loader', function () {
  151. describe('get i18n block', function () {
  152. it('basic', function () {
  153. const rs = getI18nBlock(`sfdsf<i18n>
  154. a:
  155. en: en_a
  156. zh-CN: zh-CN_a
  157. </i18n>sdfdsf`)
  158. expect(rs.a.en).to.equal('en_a')
  159. })
  160. it('return empty object for wrong format', function () {
  161. const rs = getI18nBlock(`sfdsf<i18n>
  162. a:
  163. en: en_a
  164. zh-CN: zh-CN_a
  165. </i18n>sdfdsf`)
  166. expect(JSON.stringify(rs)).to.equal('{}')
  167. })
  168. it('with locale', function () {
  169. const rs = getI18nBlockWithLocale({code: `sfdsf<i18n>
  170. a:
  171. en: en_a
  172. zh-CN: zh-CN_a
  173. </i18n>sdfdsf`,
  174. locale: 'en'})
  175. expect(rs.a).to.equal('en_a')
  176. })
  177. })
  178. describe('parse i18n for js', function () {
  179. const rs = i18nParserForScript(`this.$t('a')`, {})
  180. expect(rs).to.equal(`'a'`)
  181. })
  182. describe('parse i18n', function () {
  183. const map = {
  184. a: 'A',
  185. b: 'B',
  186. c: 'C',
  187. d: 'D'
  188. }
  189. const source1 = `<div :a="$t('a')">
  190. <p :c="d" e="f" g="hh">
  191. <span :options="['a', 'b', 'c', 'd']"></span>
  192. <span :obj="{a:'aa',b:'bb', c:$t('ee')}"></span>
  193. <span v-html="$t('sdfsdf')"></span>
  194. <span v-html="$t('sdfsdf') + $t('sfowewf') + $t('92fdf')"></span>
  195. {{ $t('dd') }}
  196. </p>
  197. </div>`
  198. const cases = [{
  199. raw: `<div :a="$t('a')"></div>`,
  200. rs: `<div :a="'A'"></div>`
  201. }, {
  202. raw: `<div :a="$t('x')"></div>`,
  203. rs: `<div :a="'x'"></div>`
  204. }, {
  205. raw: `<div :a='$t("a")'></div>`,
  206. rs: `<div :a="'A'"></div>`
  207. }, {
  208. raw: `<div v-html="$t('a')"></div>`,
  209. rs: `<div v-html="'A'"></div>`
  210. }, {
  211. raw: `<div :a="$t('a') + $t('b')"></div>`,
  212. rs: `<div :a="'A' + 'B'"></div>`
  213. }, {
  214. raw: `<div :a="$t('a') + 'B'"></div>`,
  215. rs: `<div :a="'A' + 'B'"></div>`
  216. }, {
  217. raw: `<div :a="$t('a') * 'B'"></div>`,
  218. rs: `<div :a="'A' * 'B'"></div>`
  219. }, {
  220. raw: `<div :a="{c: $t('a')}"></div>`,
  221. rs: `<div :a="{ c: 'A' }"></div>`
  222. }, {
  223. raw: `<div :a="{c: $t('a') + 'B'}"></div>`,
  224. rs: `<div :a="{ c: 'A' + 'B' }"></div>`
  225. }, {
  226. raw: `<div :a="{c: $t('a') + 'B', b: 'C' + $t('b')}"></div>`,
  227. rs: `<div :a="{
  228. c: 'A' + 'B',
  229. b: 'C' + 'B' }"></div>`
  230. }, {
  231. raw: `<div :a="[$t('a')]"></div>`,
  232. rs: `<div :a="['A']"></div>`
  233. }, {
  234. raw: `<div :a="[$t('a'),$t('b')]"></div>`,
  235. rs: `<div :a="[
  236. 'A',
  237. 'B' ]"></div>`
  238. }, {
  239. raw: `xx {{ $t('a') }}`,
  240. rs: `xx A`
  241. }, {
  242. raw: `xx {{ $t('a') }} {{$t('b')}}`,
  243. rs: `xx A B`
  244. }, {
  245. raw: `xx {{ $t('a') }} {{ $t('dfsf' + 'dsfdsf') }}`,
  246. rs: `xx A {{ $t('dfsf' + 'dsfdsf') }}`
  247. }, {
  248. raw: `xx {{ $tt('a') }}`,
  249. rs: `xx {{ $tt('a') }}`
  250. }]
  251. cases.forEach((one, index) => {
  252. it(`test ${index + 1}`, function () {
  253. const rs = i18nParser(one.raw, map)
  254. expect(rs === one.rs).to.equal(true)
  255. })
  256. })
  257. })
  258. describe('parse virtual component', function () {
  259. const parse = require('../src/libs/parse-virtual-component')
  260. it('basic', function () {
  261. const source = `<x-icon type="arrow-up-b" size="10" v-if="0 == 0"></x-icon>`
  262. const processed = parse(source, 'x-icon', function (query, a) {
  263. return '<svg ' + query.stringList + '></svg>'
  264. })
  265. expect(processed).to.equal('<svg type="arrow-up-b" size="10" v-if="0 == 0"></svg>')
  266. })
  267. it('basic', function () {
  268. const source = `<x-icon type="arrow-up-b" size="10" v-if="0 == 0"/>`
  269. const processed = parse(source, 'x-icon', function (query, a) {
  270. return '<svg ' + query.stringList + '></svg>'
  271. })
  272. expect(processed).to.equal('<svg type="arrow-up-b" size="10" v-if="0 == 0"></svg>')
  273. })
  274. it('basic', function () {
  275. const source = `<x-icon
  276. type="arrow-up-b" size="10"
  277. v-if="0 == 0"/>`
  278. const processed = parse(source, 'x-icon', function (query, a) {
  279. return '<svg ' + query.stringList + '></svg>'
  280. })
  281. expect(processed).to.equal('<svg type="arrow-up-b" size="10" v-if="0 == 0"></svg>')
  282. })
  283. it('basic', function () {
  284. const source = `<x-icon type="ios-ionic-outline" size="30"/>
  285. <x-icon type="ios-ionic-outline" size="30"></x-icon>`
  286. const processed = parse(source, 'x-icon', function (query, a) {
  287. return '<svg ' + query.stringList + '></svg>'
  288. })
  289. expect(processed).to.equal(`<svg type="ios-ionic-outline" size="30"></svg>
  290. <svg type="ios-ionic-outline" size="30"></svg>`)
  291. })
  292. })
  293. describe('parse virtual component with break line', function () {
  294. const parse = require('../src/libs/parse-virtual-component')
  295. it('basic', function () {
  296. const source = `<x-icon a="b"
  297. type="arrow-up-b"
  298. size="10"
  299. v-if="0 == 0"></x-icon>`
  300. const processed = parse(source, 'x-icon', function (query, a) {
  301. return '<svg ' + query.stringList + '></svg>'
  302. })
  303. expect(processed).to.equal('<svg a="b" type="arrow-up-b" size="10" v-if="0 == 0"></svg>')
  304. })
  305. })
  306. describe('parse virtual component', function () {
  307. const parse = require('../src/libs/parse-virtual-component')
  308. it('basic', function () {
  309. const source = `<x-icon a="b" c="d" class="e f" slot="icon"></x-icon>`
  310. const processed = parse(source, 'x-icon', function (query, a) {
  311. return '<svg ' + query.stringList + '></svg>'
  312. })
  313. expect(processed).to.equal('<svg a="b" c="d" class="e f" slot="icon"></svg>')
  314. })
  315. })
  316. describe('parse virtual component with click event', function () {
  317. const parse = require('../src/libs/parse-virtual-component')
  318. it('basic', function () {
  319. const source = `<x-icon a="b" c="d" class="e f" slot="icon" @click.native="handler"></x-icon>`
  320. const processed = parse(source, 'x-icon', function (query, a) {
  321. return '<svg ' + query.stringList + '></svg>'
  322. })
  323. expect(processed).to.equal('<svg a="b" c="d" class="e f" slot="icon" @click="handler"></svg>')
  324. })
  325. })
  326. describe('lib:get theme variables', function () {
  327. it('basic', function () {
  328. const rs = themeParse(path.resolve(__dirname, './vux-fixtures/less-theme-001.less'))
  329. expect(rs.a).to.equal('b')
  330. })
  331. it('ignore comments', function () {
  332. const rs = themeParse(path.resolve(__dirname, './vux-fixtures/less-theme-002.less'))
  333. expect(rs.a).to.equal('b')
  334. expect(rs.c).to.equal('d')
  335. expect(rs.d).to.equal('e')
  336. expect(rs.f).to.equal('g')
  337. })
  338. it('import files', function () {
  339. const rs = themeParse(path.resolve(__dirname, './vux-fixtures/less-theme-import.less'))
  340. expect(rs.x).to.equal('x')
  341. expect(rs.y).to.equal('z')
  342. })
  343. })
  344. describe('lib:import-parser', function () {
  345. let tests = [{
  346. title: 'basic',
  347. string: `import {A,B} from 'vux'`,
  348. rs: ['A', 'B']
  349. }, {
  350. title: 'basic',
  351. string: `import {A,B,} from 'vux'`,
  352. rs: ['A', 'B']
  353. }, {
  354. title: 'without space',
  355. string: `import{A,B} from 'vux'`,
  356. rs: ['A', 'B']
  357. }, {
  358. title: 'without space 2',
  359. string: `import {A,B}from 'vux'`,
  360. rs: ['A', 'B']
  361. }, {
  362. title: 'without space 3',
  363. string: `import{A,B}from 'vux'`,
  364. rs: ['A', 'B']
  365. }, {
  366. title: 'do not parse comments',
  367. string: `// import {A,B} from 'vux'
  368. import { C, D} from 'vux'`,
  369. rs: `\nimport { C, D } from 'vux'`
  370. }, {
  371. title: 'use as',
  372. string: `import {A,B as C} from 'vux'`,
  373. rs: ['A', 'C']
  374. }, {
  375. title: 'double quote',
  376. string: `import {A,B} from "vux"`,
  377. rs: ['A', 'B']
  378. }, {
  379. title: 'multi line and single quote',
  380. string: `import { A,
  381. B } from 'vux'`,
  382. rs: ['A', 'B']
  383. }, {
  384. title: 'multi line and double quote',
  385. string: `import { A,
  386. B } from "vux"`,
  387. rs: ['A', 'B']
  388. }, {
  389. title: 'no match',
  390. string: `import {A,B} from 'vvv'`,
  391. rs: `import {A,B} from 'vvv'`
  392. }, {
  393. title: 'more codes',
  394. string: `import C from 'XY'
  395. import { D } from 'ZW'
  396. import {A,B} from 'vvv'
  397. import { C } from 'vux'`,
  398. rs: `import C from 'XY'
  399. import { D } from 'ZW'
  400. import {A,B} from 'vvv'
  401. import { C } from 'vux'`
  402. }, {
  403. title: 'vux test2',
  404. string: `import {Group,Cell} from 'vux'
  405. import value2name from 'vux/src/filters/value2name'`,
  406. rs: `import { Group, Cell } from 'vux'
  407. import value2name from 'vux/src/filters/value2name'`
  408. }, {
  409. title: 'vux test3',
  410. string: `import {Group,
  411. Cell} from 'vux'
  412. import value2name from 'vux/src/filters/value2name'`,
  413. rs: `import { Group, Cell } from 'vux'
  414. import value2name from 'vux/src/filters/value2name'`
  415. }, {
  416. title: 'vux test4',
  417. string: `import { M1, M2 } from 'vux'
  418. import { mapMutations, mapState } from 'vuex'
  419. import { Group, Cell } from 'vux'
  420. import { Group1, Cell1 } from 'vux'
  421. import value2name from 'vux/src/filters/value2name'`,
  422. rs: `import { M1, M2 } from 'vux'
  423. import { mapMutations, mapState } from 'vuex'
  424. import { Group, Cell } from 'vux'
  425. import { Group1, Cell1 } from 'vux'
  426. import value2name from 'vux/src/filters/value2name'`
  427. }, {
  428. title: 'vux test5',
  429. string: `import {
  430. XX,
  431. YY} from 'vux'`,
  432. rs: `import { XX, YY } from 'vux'`
  433. }, {
  434. title: 'vux test6',
  435. string: `/**/
  436. import {Divider } from 'vux'`,
  437. rs: `/**/
  438. import { Divider } from 'vux'`
  439. }]
  440. tests.forEach(function (one) {
  441. it(one.title, function () {
  442. const rs = parse(one.string, commomMapper)
  443. if (typeof one.rs === 'string') {
  444. expect(rs).to.equal(one.rs)
  445. } else {
  446. expect(rs).to.equal(`import { ${one.rs.join(', ')} } from 'vux'`)
  447. }
  448. })
  449. })
  450. it('vux test', function () {
  451. const rs = parse(`import {AlertPlugin, ToastPlugin} from 'vux'`, vuxMapper)
  452. expect(rs).to.equal(`import AlertPlugin from 'vux/src/plugins/Alert'
  453. import ToastPlugin from 'vux/src/plugins/Toast'
  454. `)
  455. })
  456. it('vux test7', function () {
  457. const rs = parse(`import {AlertPlugin, ToastPlugin} from 'vux'
  458. // import { AlertPlugin } from 'vux'`, vuxMapper)
  459. expect(rs).to.equal(`import AlertPlugin from 'vux/src/plugins/Alert'
  460. import ToastPlugin from 'vux/src/plugins/Toast'
  461. `)
  462. })
  463. it('issue #1579 (1)', function () {
  464. const rs = parse(`import {
  465. AlertPlugin,
  466. ToastPlugin
  467. } from 'vux';`, vuxMapper)
  468. expect(rs).to.equal(`import AlertPlugin from 'vux/src/plugins/Alert'
  469. import ToastPlugin from 'vux/src/plugins/Toast'
  470. `)
  471. })
  472. it('issue #1579 (2)', function () {
  473. const rs = parse(`import {AlertPlugin,
  474. ToastPlugin
  475. } from 'vux'`, vuxMapper)
  476. expect(rs).to.equal(`import AlertPlugin from 'vux/src/plugins/Alert'
  477. import ToastPlugin from 'vux/src/plugins/Toast'
  478. `)
  479. })
  480. })
  481. describe('plugin:less-theme', function () {
  482. it('basic', function (done) {
  483. test({
  484. entry: './test/vux-fixtures/less-theme-basic.vue'
  485. }, {
  486. plugins: [{
  487. name: 'less-theme',
  488. path: './test/vux-fixtures/less-theme-basic.less'
  489. }]
  490. }, function (window, module, rawModule) {
  491. var vnode = mockRender(module, {
  492. msg: 'hi'
  493. })
  494. expect(vnode.tag).to.equal('p')
  495. var styles = window.document.querySelectorAll('style')
  496. expect(styles[0].textContent).to.contain('\n.p {\n color: red;\n}\n')
  497. done()
  498. })
  499. })
  500. })
  501. describe('plugin:style-parser', function () {
  502. it('basic', function (done) {
  503. test({
  504. entry: './test/vux-fixtures/style-parser-basic.vue'
  505. }, {
  506. plugins: [{
  507. name: 'less-theme',
  508. path: './test/vux-fixtures/less-theme-basic.less'
  509. }, {
  510. name: 'style-parser',
  511. fn: function (source) {
  512. return source.replace('@theme-p-color', 'yellow')
  513. }
  514. }]
  515. }, function (window, module, rawModule) {
  516. var vnode = mockRender(module, {
  517. msg: 'hi'
  518. })
  519. expect(vnode.tag).to.equal('p')
  520. var styles = window.document.querySelectorAll('style')
  521. expect(styles[0].textContent).to.contain('\n.p {\n color: yellow;\n}\n')
  522. done()
  523. })
  524. })
  525. })
  526. describe('plugin:template-feature-switch', function () {
  527. it('basic', function (done) {
  528. test({
  529. entry: './test/vux-fixtures/template-feature-switch-basic.vue'
  530. }, {
  531. plugins: [{
  532. name: 'template-feature-switch',
  533. features: {
  534. FEATURE1: true,
  535. FEATURE2: false
  536. }
  537. }]
  538. }, function (window, module, rawModule) {
  539. var vnode = mockRender(module, {
  540. msg: 'hi'
  541. })
  542. expect(vnode.tag).to.equal('div')
  543. expect(vnode.children[0].indexOf('ON FEATURE1') > -1).to.equal(true)
  544. expect(vnode.children[0].indexOf('OFF FEATURE2') > -1).to.equal(true)
  545. done()
  546. })
  547. })
  548. })
  549. describe('one instance', function () {
  550. it('should throw', function () {
  551. const webpackConfig = {
  552. plugins: []
  553. }
  554. const merge = function () {
  555. return vuxLoader.merge(webpackConfig, {
  556. options: {
  557. env: 'env1'
  558. },
  559. plugins: [{
  560. name: 'test1'
  561. }, {
  562. name: 'test1'
  563. }]
  564. })
  565. }
  566. expect(merge).to.throw(/only one instance is allowed/)
  567. })
  568. })
  569. describe('merge multi times', function () {
  570. it('should merge options', function () {
  571. const webpackConfig = {
  572. plugins: []
  573. }
  574. const config1 = vuxLoader.merge(webpackConfig, {
  575. options: {
  576. env: 'env1'
  577. }
  578. })
  579. expect(getOptionsPlugin(config1).options.vux.options.env).to.equal('env1')
  580. const config2 = vuxLoader.merge(config1, {
  581. options: {
  582. env: 'env2'
  583. }
  584. })
  585. expect(getOptionsPlugin(config2).options.vux.options.env).to.equal('env2')
  586. })
  587. it('should merge plugins with the same name', function () {
  588. const webpackConfig = {}
  589. const config1 = vuxLoader.merge(webpackConfig, {
  590. plugins: [{
  591. name: 'test1',
  592. arg: 1
  593. }]
  594. })
  595. expect(getOptionsPlugin(config1).options.vux.plugins.length).to.equal(1)
  596. expect(getOptionsPlugin(config1).options.vux.plugins[0].arg).to.equal(1)
  597. const config2 = vuxLoader.merge(config1, {
  598. plugins: [{
  599. name: 'test1',
  600. arg: 2
  601. }]
  602. })
  603. expect(getOptionsPlugin(config1).options.vux.plugins.length).to.equal(1)
  604. expect(getOptionsPlugin(config1).options.vux.plugins[0].arg).to.equal(2)
  605. })
  606. it('should delete plugin when env is change', function () {
  607. const webpackConfig = {}
  608. const config1 = vuxLoader.merge(webpackConfig, {
  609. options: {
  610. env: 'env1'
  611. },
  612. plugins: [{
  613. name: 'test1',
  614. arg: 1,
  615. envs: ['env1']
  616. }]
  617. })
  618. expect(config1.plugins[0].options.vux.plugins.length).to.equal(1)
  619. const config2 = vuxLoader.merge(config1, {
  620. options: {
  621. env: 'env2'
  622. }
  623. })
  624. expect(getOptionsPlugin(config1).options.vux.plugins.length).to.equal(0)
  625. })
  626. it('should merge plugins', function () {
  627. const webpackConfig = {}
  628. const config1 = vuxLoader.merge(webpackConfig, {
  629. options: {
  630. env: 'env1'
  631. },
  632. plugins: [{
  633. name: 'test1',
  634. arg: 1,
  635. envs: ['env1']
  636. }]
  637. })
  638. expect(getOptionsPlugin(config1).options.vux.allPlugins.length).to.equal(1)
  639. expect(getOptionsPlugin(config1).options.vux.plugins.length).to.equal(1)
  640. const config2 = vuxLoader.merge(config1, {
  641. plugins: [{
  642. name: 'test2'
  643. }]
  644. })
  645. expect(getOptionsPlugin(config2).options.vux.allPlugins.length).to.equal(2)
  646. expect(getOptionsPlugin(config2).options.vux.plugins.length).to.equal(2)
  647. const config3 = vuxLoader.merge(config2, {
  648. plugins: [{
  649. name: 'test3',
  650. envs: ['env3']
  651. }]
  652. })
  653. expect(getOptionsPlugin(config3).options.vux.allPlugins.length).to.equal(3)
  654. expect(getOptionsPlugin(config3).options.vux.plugins.length).to.equal(2)
  655. })
  656. })
  657. describe('plugin:script-parser', function () {
  658. it('fn function should work', function (done) {
  659. test({
  660. entry: './test/vux-fixtures/script-parser-fn.vue'
  661. }, {
  662. plugins: [{
  663. name: 'script-parser',
  664. fn: function (source) {
  665. return source.replace('AAAA', 'BBBB')
  666. }
  667. }]
  668. }, function (window, module, rawModule) {
  669. var vnode = mockRender(module, {
  670. msg: 'hi'
  671. })
  672. expect(vnode.tag).to.equal('p')
  673. expect(module.data().msg).to.equal('BBBB')
  674. done()
  675. })
  676. })
  677. it('fn function should not work with env', function (done) {
  678. test({
  679. entry: './test/vux-fixtures/script-parser-fn.vue'
  680. }, {
  681. options: {
  682. env: 'test'
  683. },
  684. plugins: [{
  685. name: 'script-parser',
  686. envs: ['production'],
  687. fn: function (source) {
  688. return source.replace('AAAA', 'BBBB')
  689. }
  690. }]
  691. }, function (window, module, rawModule) {
  692. var vnode = mockRender(module, {
  693. msg: 'hi'
  694. })
  695. expect(vnode.tag).to.equal('p')
  696. expect(module.data().msg).to.equal('AAAA')
  697. done()
  698. })
  699. })
  700. })
  701. describe('plugin:template-parser', function () {
  702. it('fn function should work', function (done) {
  703. test({
  704. entry: './test/vux-fixtures/template-parser-fn.vue'
  705. }, {
  706. plugins: [{
  707. name: 'template-parser',
  708. fn: function (source) {
  709. return source.replace('我们没有底线', '我是有底线的')
  710. }
  711. }]
  712. }, function (window, module, rawModule) {
  713. var vnode = mockRender(module, {
  714. msg: 'hi'
  715. })
  716. expect(vnode.tag).to.equal('p')
  717. expect(vnode.children[0]).to.equal('我是有底线的')
  718. done()
  719. })
  720. })
  721. it('replaceList param should work', function (done) {
  722. test({
  723. entry: './test/vux-fixtures/template-parser-fn.vue'
  724. }, {
  725. plugins: [{
  726. name: 'template-parser',
  727. replaceList: [{
  728. test: /我们没有/,
  729. replaceString: ''
  730. }, {
  731. test: /底线/,
  732. replaceString: '底线是什么'
  733. }]
  734. }]
  735. }, function (window, module, rawModule) {
  736. var vnode = mockRender(module, {
  737. msg: 'hi'
  738. })
  739. expect(vnode.tag).to.equal('p')
  740. expect(vnode.children[0]).to.equal('底线是什么')
  741. done()
  742. })
  743. })
  744. })
  745. })
  746. /**
  747. describe.skip('vue-loader', function () {
  748. it('basic', function (done) {
  749. test({
  750. entry: './test/fixtures/basic.vue'
  751. }, function (window, module, rawModule) {
  752. var vnode = mockRender(module, {
  753. msg: 'hi'
  754. })
  755. // <h2 class="red">{{msg}}</h2>
  756. expect(vnode.tag).to.equal('h2')
  757. expect(vnode.data.staticClass).to.equal('red')
  758. expect(vnode.children[0]).to.equal('hi')
  759. expect(module.data().msg).to.contain('Hello from Component A!')
  760. var style = window.document.querySelector('style').textContent
  761. style = normalizeNewline(style)
  762. expect(style).to.contain('comp-a h2 {\n color: #f00;\n}')
  763. done()
  764. })
  765. })
  766. it('expose filename', function (done) {
  767. test({
  768. entry: './test/fixtures/basic.vue'
  769. }, function (window, module, rawModule) {
  770. expect(module.__file).to.equal(path.resolve(__dirname, './fixtures/basic.vue'))
  771. done()
  772. })
  773. })
  774. it('pre-processors', function (done) {
  775. test({
  776. entry: './test/fixtures/pre.vue'
  777. }, function (window, module) {
  778. var vnode = mockRender(module)
  779. // div
  780. // h1 This is the app
  781. // comp-a
  782. // comp-b
  783. expect(vnode.children[0].tag).to.equal('h1')
  784. expect(vnode.children[1].tag).to.equal('comp-a')
  785. expect(vnode.children[2].tag).to.equal('comp-b')
  786. expect(module.data().msg).to.contain('Hello from coffee!')
  787. var style = window.document.querySelector('style').textContent
  788. expect(style).to.contain('body {\n font: 100% Helvetica, sans-serif;\n color: #999;\n}')
  789. done()
  790. })
  791. })
  792. it('scoped style', function (done) {
  793. test({
  794. entry: './test/fixtures/scoped-css.vue'
  795. }, function (window, module) {
  796. var id = 'data-v-' + genId(require.resolve('./fixtures/scoped-css.vue'))
  797. expect(module._scopeId).to.equal(id)
  798. var vnode = mockRender(module, {
  799. ok: true
  800. })
  801. // <div>
  802. // <div><h1>hi</h1></div>
  803. // <p class="abc def">hi</p>
  804. // <template v-if="ok"><p class="test">yo</p></template>
  805. // <svg><template><p></p></template></svg>
  806. // </div>
  807. expect(vnode.children[0].tag).to.equal('div')
  808. expect(vnode.children[1]).to.equal(' ')
  809. expect(vnode.children[2].tag).to.equal('p')
  810. expect(vnode.children[2].data.staticClass).to.equal('abc def')
  811. expect(vnode.children[4][0].tag).to.equal('p')
  812. expect(vnode.children[4][0].data.staticClass).to.equal('test')
  813. var style = window.document.querySelector('style').textContent
  814. style = normalizeNewline(style)
  815. expect(style).to.contain('.test[' + id + '] {\n color: yellow;\n}')
  816. expect(style).to.contain('.test[' + id + ']:after {\n content: \'bye!\';\n}')
  817. expect(style).to.contain('h1[' + id + '] {\n color: green;\n}')
  818. done()
  819. })
  820. })
  821. it('style import', function (done) {
  822. test({
  823. entry: './test/fixtures/style-import.vue'
  824. }, function (window) {
  825. var styles = window.document.querySelectorAll('style')
  826. expect(styles[0].textContent).to.contain('h1 { color: red;\n}')
  827. // import with scoped
  828. var id = 'data-v-' + genId(require.resolve('./fixtures/style-import.vue'))
  829. expect(styles[1].textContent).to.contain('h1[' + id + '] { color: green;\n}')
  830. done()
  831. })
  832. })
  833. it('template import', function (done) {
  834. test({
  835. entry: './test/fixtures/template-import.vue'
  836. }, function (window, module) {
  837. var vnode = mockRender(module)
  838. // '<div><h1>hello</h1></div>'
  839. expect(vnode.children[0].tag).to.equal('h1')
  840. expect(vnode.children[0].children[0]).to.equal('hello')
  841. done()
  842. })
  843. })
  844. it('script import', function (done) {
  845. test({
  846. entry: './test/fixtures/script-import.vue'
  847. }, function (window, module) {
  848. expect(module.data().msg).to.contain('Hello from Component A!')
  849. done()
  850. })
  851. })
  852. it('source map', function (done) {
  853. var config = Object.assign({}, globalConfig, {
  854. entry: './test/fixtures/basic.vue',
  855. devtool: '#source-map'
  856. })
  857. bundle(config, function (code) {
  858. var map = mfs.readFileSync('/test.build.js.map').toString()
  859. var smc = new SourceMapConsumer(JSON.parse(map))
  860. var line
  861. var col
  862. var targetRE = /^\s+msg: 'Hello from Component A!'/
  863. code.split(/\r?\n/g).some(function (l, i) {
  864. if (targetRE.test(l)) {
  865. line = i + 1
  866. col = 0
  867. return true
  868. }
  869. })
  870. var pos = smc.originalPositionFor({
  871. line: line,
  872. column: col
  873. })
  874. expect(pos.source.indexOf('basic.vue') > -1)
  875. expect(pos.line).to.equal(9)
  876. done()
  877. })
  878. })
  879. it('media-query', function (done) {
  880. test({
  881. entry: './test/fixtures/media-query.vue'
  882. }, function (window) {
  883. var style = window.document.querySelector('style').textContent
  884. style = normalizeNewline(style)
  885. var id = 'data-v-' + genId(require.resolve('./fixtures/media-query.vue'))
  886. expect(style).to.contain('@media print {\n.foo[' + id + '] {\n color: #000;\n}\n}')
  887. done()
  888. })
  889. })
  890. it.skip('extract CSS', function (done) {
  891. bundle(Object.assign({}, globalConfig, {
  892. entry: './test/fixtures/extract-css.vue',
  893. vue: {
  894. loaders: {
  895. css: ExtractTextPlugin.extract('css-loader'),
  896. stylus: ExtractTextPlugin.extract('css-loader?sourceMap!stylus-loader')
  897. }
  898. },
  899. plugins: [
  900. new ExtractTextPlugin('test.output.css')
  901. ]
  902. }), function () {
  903. var css = mfs.readFileSync('/test.output.css').toString()
  904. css = normalizeNewline(css)
  905. expect(css).to.contain('h1 {\n color: #f00;\n}\n\nh2 {\n color: green;\n}')
  906. done()
  907. })
  908. })
  909. it.skip('dependency injection', function (done) {
  910. test({
  911. entry: './test/fixtures/inject.js'
  912. }, function (window) {
  913. // console.log(window.injector.toString())
  914. var module = interopDefault(window.injector({
  915. './service': {
  916. msg: 'Hello from mocked service!'
  917. }
  918. }))
  919. var vnode = mockRender(module, module.data())
  920. // <div class="msg">{{ msg }}</div>
  921. expect(vnode.tag).to.equal('div')
  922. expect(vnode.data.staticClass).to.equal('msg')
  923. expect(vnode.children[0]).to.equal('Hello from mocked service!')
  924. done()
  925. })
  926. })
  927. it('translates relative URLs and respects resolve alias', function (done) {
  928. test({
  929. entry: './test/fixtures/resolve.vue',
  930. resolve: {
  931. alias: {
  932. fixtures: path.resolve(__dirname, 'fixtures')
  933. }
  934. },
  935. module: {
  936. rules: [
  937. {
  938. test: /\.vue$/,
  939. loader: loaderPath
  940. },
  941. {
  942. test: /\.png$/,
  943. loader: 'file-loader?name=[name].[hash:6].[ext]'
  944. }
  945. ]
  946. }
  947. }, function (window, module) {
  948. var vnode = mockRender(module)
  949. // <div>
  950. // <img src="logo.c9e00e.png">
  951. // <img src="logo.c9e00e.png">
  952. // </div>
  953. expect(vnode.children[0].tag).to.equal('img')
  954. expect(vnode.children[0].data.attrs.src).to.equal('logo.c9e00e.png')
  955. expect(vnode.children[2].tag).to.equal('img')
  956. expect(vnode.children[2].data.attrs.src).to.equal('logo.c9e00e.png')
  957. var style = window.document.querySelector('style').textContent
  958. expect(style).to.contain('html { background-image: url(logo.c9e00e.png);\n}')
  959. expect(style).to.contain('body { background-image: url(logo.c9e00e.png);\n}')
  960. done()
  961. })
  962. })
  963. it('postcss options', function (done) {
  964. test({
  965. entry: './test/fixtures/postcss.vue',
  966. vue: {
  967. postcss: {
  968. options: {
  969. parser: require('sugarss')
  970. }
  971. }
  972. }
  973. }, function (window) {
  974. var style = window.document.querySelector('style').textContent
  975. style = normalizeNewline(style)
  976. expect(style).to.contain('h1 {\n color: red;\n font-size: 14px\n}')
  977. done()
  978. })
  979. })
  980. it('transpile ES2015 features in template', function (done) {
  981. test({
  982. entry: './test/fixtures/es2015.vue'
  983. }, function (window, module) {
  984. var vnode = mockRender(module, {
  985. a: 'hello',
  986. b: true
  987. })
  988. // <div :class="{[a]:true}"></div>
  989. expect(vnode.tag).to.equal('div')
  990. expect(vnode.data.class['test-hello']).to.equal(true)
  991. expect(vnode.data.class['b']).to.equal(true)
  992. done()
  993. })
  994. })
  995. it('allows to export extended constructor', function (done) {
  996. test({
  997. entry: './test/fixtures/extend.vue'
  998. }, function (window, Module) {
  999. // extend.vue should export Vue constructor
  1000. var vnode = mockRender(Module.options, {
  1001. msg: 'success'
  1002. })
  1003. expect(vnode.tag).to.equal('div')
  1004. expect(vnode.children[0]).to.equal('success')
  1005. expect(new Module().msg === 'success')
  1006. done()
  1007. })
  1008. })
  1009. it('support es compatible modules', function (done) {
  1010. test({
  1011. entry: './test/fixtures/basic.vue',
  1012. vue: {
  1013. esModule: true
  1014. }
  1015. }, function (window, module, rawModule) {
  1016. expect(rawModule.__esModule).to.equal(true)
  1017. var vnode = mockRender(rawModule.default, {
  1018. msg: 'hi'
  1019. })
  1020. expect(vnode.tag).to.equal('h2')
  1021. expect(vnode.data.staticClass).to.equal('red')
  1022. expect(vnode.children[0]).to.equal('hi')
  1023. expect(rawModule.default.data().msg).to.contain('Hello from Component A!')
  1024. done()
  1025. })
  1026. })
  1027. it('css-modules', function (done) {
  1028. function testWithIdent(localIdentName, regexToMatch, cb) {
  1029. test({
  1030. entry: './test/fixtures/css-modules.vue',
  1031. vue: {
  1032. cssModules: localIdentName && {
  1033. localIdentName: localIdentName
  1034. }
  1035. }
  1036. }, function (window) {
  1037. var module = window.vueModule
  1038. // get local class name
  1039. var className = module.computed.style().red
  1040. expect(className).to.match(regexToMatch)
  1041. // class name in style
  1042. var style = [].slice.call(window.document.querySelectorAll('style')).map(function (style) {
  1043. return style.textContent
  1044. }).join('\n')
  1045. style = normalizeNewline(style)
  1046. expect(style).to.contain('.' + className + ' {\n color: red;\n}')
  1047. // animation name
  1048. var match = style.match(/@keyframes\s+(\S+)\s+{/)
  1049. expect(match).to.have.length(2)
  1050. var animationName = match[1]
  1051. expect(animationName).to.not.equal('fade')
  1052. expect(style).to.contain('animation: ' + animationName + ' 1s;')
  1053. // default module + pre-processor + scoped
  1054. var anotherClassName = module.computed.$style().red
  1055. expect(anotherClassName).to.match(regexToMatch).and.not.equal(className)
  1056. var id = 'data-v-' + genId(require.resolve('./fixtures/css-modules.vue'))
  1057. expect(style).to.contain('.' + anotherClassName + '[' + id + ']')
  1058. cb()
  1059. })
  1060. }
  1061. // default localIdentName
  1062. testWithIdent(undefined, /^\w{23}/, function () {
  1063. // specified localIdentName
  1064. var ident = '[path][name]---[local]---[hash:base64:5]'
  1065. var regex = /^test-fixtures-css-modules---red---\w{5}/
  1066. testWithIdent(ident, regex, done)
  1067. })
  1068. })
  1069. it('css-modules in SSR', function (done) {
  1070. bundle({
  1071. entry: './test/fixtures/css-modules.vue',
  1072. target: 'node',
  1073. output: Object.assign({}, globalConfig.output, {
  1074. libraryTarget: 'commonjs2'
  1075. })
  1076. }, function (code) {
  1077. // http://stackoverflow.com/questions/17581830/load-node-js-module-from-string-in-memory
  1078. function requireFromString(src, filename) {
  1079. var Module = module.constructor;
  1080. var m = new Module();
  1081. m._compile(src, filename);
  1082. return m.exports;
  1083. }
  1084. var output = requireFromString(code, './test.build.js')
  1085. expect(output.computed.style().red).to.exist
  1086. done()
  1087. })
  1088. })
  1089. })
  1090. **/