Skip to content

feat: add support for vue-i18n custom blocks by default #227

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,65 @@ vue-jest compiles the script and template of SFCs into a JavaScript file that Je

You can change the behavior of `vue-jest` by using `jest.globals`.

#### Supporting custom blocks

A great feature of the Vue SFC compiler is that it can support custom blocks. You might want to use those blocks in your tests. To render out custom blocks for testing purposes, you'll need to write a transformer. Once you have your transformer, you'll add an entry to vue-jest's transform map. This is how [vue-i18n's](https://github.com/kazupon/vue-i18n) `<i18n>` custom blocks are supported in unit tests.

A `package.json` Example

```json
{
"jest": {
"moduleFileExtensions": ["js", "json", "vue"],
"transform": {
"^.+\\.js$": "babel-jest",
"^.+\\.vue$": "vue-jest"
},
"globals": {
"vue-jest": {
"transform": {
"your-custom-block": "./custom-block-processor.js"
}
}
}
}
}
```

> _Tip:_ Need programmatic configuration? Use the [--config](https://jestjs.io/docs/en/cli.html#config-path) option in Jest CLI, and export a `.js` file

A `jest.config.js` Example - If you're using a dedicated configuration file like you can reference and require your processor in the config file instead of using a file reference.

```js
module.exports = {
globals: {
'vue-jest': {
transform: {
'your-custom-block': require('./custom-block-processor')
}
}
}
}
```

#### Writing a processor

Processors must return an object with a "process" method, like so...

```js
module.exports = {
/**
* Process the content inside of a custom block and prepare it for execution in a testing environment
* @param {SFCCustomBlock[]} blocks All of the blocks matching your type, returned from `@vue/component-compiler-utils`
* @param {string} vueOptionsNamespace The internal namespace for a component's Vue Options in vue-jest
* @param {string} filename The SFC file being processed
* @param {Object} config The full Jest config
* @returns {string} The code to be output after processing all of the blocks matched by this type
*/
process(blocks, vueOptionsNamepsace, filename, config) {}
}
```

#### babelConfig

Provide `babelConfig` in one of the following formats:
Expand Down
6 changes: 6 additions & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
vueOptionsNamespace: '__options__',
defaultVueJestConfig: {
transform: {}
}
}
23 changes: 15 additions & 8 deletions lib/generate-code.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
const namespace = require('./constants').vueOptionsNamespace

const splitRE = /\r?\n/g

module.exports = function generateCode(
scriptResult,
templateResult,
stylesResult,
customBlocksResult,
isFunctional
) {
let output = ''
Expand All @@ -21,7 +24,7 @@ module.exports = function generateCode(
}

output +=
`var __options__ = typeof exports.default === 'function' ` +
`var ${namespace} = typeof exports.default === 'function' ` +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any specific reason to move the constant to another file? It seems to be used elsewhere when it wasn't before - why is that?

Copy link
Contributor Author

@JessicaSachs JessicaSachs Mar 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it was being hardcoded inside of vue-i18n-jest and Edd refactored this in v4, which broke vue-i18n-jest if we were to have released v4 of vue-jest... We should make any required constants passed in rather than requiring developers to dig into vue-jest source code to figure out what constants we're using when they're writing their processors

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, makes sense, sounds good

`? exports.default.options ` +
`: exports.default\n`

Expand All @@ -37,11 +40,11 @@ module.exports = function generateCode(

output +=
`__options__.render = render\n` +
`__options__.staticRenderFns = staticRenderFns\n`
`${namespace}.staticRenderFns = staticRenderFns\n`

if (isFunctional) {
output += '__options__.functional = true\n'
output += '__options__._compiled = true\n'
output += `${namespace}.functional = true\n`
output += `${namespace}._compiled = true\n`
}
}

Expand All @@ -59,22 +62,26 @@ module.exports = function generateCode(
if (isFunctional) {
output +=
`;(function() {\n` +
` var originalRender = __options__.render\n` +
` var originalRender = ${namespace}.render\n` +
` var styleFn = function () { ${styleStr} }\n` +
` __options__.render = function renderWithStyleInjection (h, context) {\n` +
` ${namespace}.render = function renderWithStyleInjection (h, context) {\n` +
` styleFn.call(context)\n` +
` return originalRender(h, context)\n` +
` }\n` +
`})()\n`
} else {
output +=
`;(function() {\n` +
` var beforeCreate = __options__.beforeCreate\n` +
` var beforeCreate = ${namespace}.beforeCreate\n` +
` var styleFn = function () { ${styleStr} }\n` +
` __options__.beforeCreate = beforeCreate ? [].concat(beforeCreate, styleFn) : [styleFn]\n` +
` ${namespace}.beforeCreate = beforeCreate ? [].concat(beforeCreate, styleFn) : [styleFn]\n` +
`})()\n`
}
}

if (customBlocksResult) {
output += `;\n ${customBlocksResult}`
}
return {
code: output,
renderFnStartLine,
Expand Down
41 changes: 41 additions & 0 deletions lib/process-custom-blocks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const { getVueJestConfig, getCustomTransformer } = require('./utils')
const vueOptionsNamespace = require('./constants').vueOptionsNamespace

function applyTransformer(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we try to match the jest process api a bit more closer? That would be `applyTransform(blocks, filename, config, transformer, vueOptionsNamespace) I guess. Although maybe more awkard to call then - just thinking out loud

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure about this either. I think if there was no external context, like if jest process didn't exist, maybe an object-based api would be best, because there's a lot of optional params.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Object based works well for me too - what do you think? I really like to use an object when there are more than 1-2 params, since it is easier to read.

If it's not too much trouble that change could go with that? I don't mind too much, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating it for this

transformer,
blocks,
vueOptionsNamespace,
filename,
config
) {
return transformer.process({ blocks, vueOptionsNamespace, filename, config })
}

function groupByType(acc, block) {
acc[block.type] = acc[block.type] || []
acc[block.type].push(block)
return acc
}

module.exports = function(allBlocks, filename, config) {
const blocksByType = allBlocks.reduce(groupByType, {})
const code = []
for (const [type, blocks] of Object.entries(blocksByType)) {
const transformer = getCustomTransformer(
getVueJestConfig(config).transform,
type
)
if (transformer) {
const codeStr = applyTransformer(
transformer,
blocks,
vueOptionsNamespace,
filename,
config
)
code.push(codeStr)
}
}

return code.length ? code.join('\n') : ''
}
11 changes: 9 additions & 2 deletions lib/process.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ const splitRE = /\r?\n/g

const VueTemplateCompiler = require('vue-template-compiler')
const generateSourceMap = require('./generate-source-map')
const typescriptTransformer = require('./typescript-transformer')
const coffeescriptTransformer = require('./coffee-transformer')
const typescriptTransformer = require('./transformers/typescript')
const coffeescriptTransformer = require('./transformers/coffee')
const _processStyle = require('./process-style')
const processCustomBlocks = require('./process-custom-blocks')
const getVueJestConfig = require('./utils').getVueJestConfig
const logResultErrors = require('./utils').logResultErrors
const stripInlineSourceMap = require('./utils').stripInlineSourceMap
Expand Down Expand Up @@ -99,6 +100,11 @@ module.exports = function(src, filename, config) {
const templateResult = processTemplate(descriptor.template, filename, config)
const scriptResult = processScript(descriptor.script, filename, config)
const stylesResult = processStyle(descriptor.styles, filename, config)
const customBlocksResult = processCustomBlocks(
descriptor.customBlocks,
filename,
config
)

const isFunctional =
descriptor.template &&
Expand All @@ -112,6 +118,7 @@ module.exports = function(src, filename, config) {
scriptResult,
templateResult,
stylesResult,
customBlocksResult,
isFunctional
)

Expand Down
6 changes: 3 additions & 3 deletions lib/coffee-transformer.js → lib/transformers/coffee.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const ensureRequire = require('./ensure-require.js')
const throwError = require('./utils').throwError
const getBabelOptions = require('./utils').getBabelOptions
const ensureRequire = require('../ensure-require.js')
const throwError = require('../utils').throwError
const getBabelOptions = require('../utils').getBabelOptions

module.exports = {
process(src, filename, config) {
Expand Down
14 changes: 8 additions & 6 deletions lib/typescript-transformer.js → lib/transformers/typescript.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
const ensureRequire = require('./ensure-require')
const ensureRequire = require('../ensure-require')
const babelJest = require('babel-jest')
const getBabelOptions = require('./utils').getBabelOptions
const getTsJestConfig = require('./utils').getTsJestConfig
const stripInlineSourceMap = require('./utils').stripInlineSourceMap
const getCustomTransformer = require('./utils').getCustomTransformer
const getVueJestConfig = require('./utils').getVueJestConfig
const {
getBabelOptions,
getTsJestConfig,
stripInlineSourceMap,
getCustomTransformer,
getVueJestConfig
} = require('../utils')

module.exports = {
process(scriptContent, filePath, config) {
Expand Down
38 changes: 26 additions & 12 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const constants = require('./constants')
const loadPartialConfig = require('@babel/core').loadPartialConfig
const chalk = require('chalk')
const path = require('path')
Expand Down Expand Up @@ -83,19 +84,32 @@ const getCustomTransformer = function getCustomTransformer(
transform = {},
lang
) {
let transformerPath = fetchTransformer(lang, transform)
if (transformerPath) {
const transformer = require(resolvePath(transformerPath))

if (!isValidTransformer(transformer)) {
throwError(
`transformer must contain at least one process, preprocess, or ` +
`postprocess method`
)
}
return transformer
transform = { ...constants.defaultVueJestConfig.transform, ...transform }

const transformerPath = fetchTransformer(lang, transform)

if (!transformerPath) {
return null
}
return null

let transformer
if (
typeof transformerPath === 'string' &&
require(resolvePath(transformerPath))
) {
transformer = require(resolvePath(transformerPath))
} else if (typeof transformerPath === 'object') {
transformer = transformerPath
}

if (!isValidTransformer(transformer)) {
throwError(
`transformer must contain at least one process, preprocess, or ` +
`postprocess method`
)
}

return transformer
}

const throwError = function error(msg) {
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"stylus": "^0.54.5",
"typescript": "^3.2.2",
"vue": "^2.4.2",
"vue-i18n": "^8.15.5",
"vue-template-compiler": "^2.4.2"
},
"peerDependencies": {
Expand All @@ -72,7 +73,8 @@
"chalk": "^2.1.0",
"convert-source-map": "^1.6.0",
"extract-from-css": "^0.4.4",
"source-map": "^0.5.6",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JessicaSachs this package is used here

const sourceMap = require('source-map')

"js-yaml": "^3.13.1",
"json5": "^2.1.1",
"ts-jest": "^24.0.0"
},
"repository": {
Expand Down
Loading