This is the 24th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021
Vuex 4 source code learning notes – mapState, mapGetters, mapActions, mapMutations auxiliary function principle (vi)
We learned how mapState works from the source code and that auxiliary functions such as mapState are not available in Vue3’s Compositon API.
Today we will learn the source of Vuex tool functions through unit tests.
First of all, in the package.json file of the Vuex project, you can see that there are many test scripts
"scripts": {
/ /...
"lint": "eslint src test"."test": "npm run lint && npm run build && npm run test:types && npm run test:unit && npm run test:ssr && npm run test:e2e && npm run test:esm"."test:unit": "jest --testPathIgnorePatterns test/e2e"."test:e2e": "start-server-and-test dev http://localhost:8080 \"jest --testPathIgnorePatterns test/unit\""."test:ssr": "cross-env VUE_ENV=server jest --testPathIgnorePatterns test/e2e"."test:types": "tsc -p types/test"."test:esm": "node test/esm/esm-test.js"."coverage": "jest --testPathIgnorePatterns test/e2e --coverage"./ /...
}
Copy the code
Today we are going to focus on test:unit
Vuex uses jEST as its unit testing framework. Jestjs.io
Let’s run it in the terminal
yarn test:unit
Copy the code
There is a catch here. When installing dependencies, it is best to use YARN instead of NPM, because Vuex uses YARN to install dependencies. There is also a yarn.lock file in the project to lock the dependency version.
When installing with NPM, the @vue/ devtools-API package installs the latest version
If yarn is installed, the @vue/devtools-api package is the version of yarn.lock
The version change of @vue/ devtools-API will cause an error when we build Vuex. It is also a small bug of Vuex, which has raised issues for Vuex.
The running results are as follows:
PASS test/unit/modules.spec.js
PASS test/unit/helpers.spec.js
PASS test/unit/store.spec.js
PASS test/unit/hot-reload.spec.js
PASS test/unit/module/module.spec.js
PASS test/unit/module/module-collection.spec.js
PASS test/unit/util.spec.js
Test Suites: 7 passed, 7 total
Tests: 102 passed, 102 total
Snapshots: 0 total
Time: 2.005 s
Ran all test suites.
Copy the code
Today we’ll start with the simplest utility function, the SRC /util.js file, which has a small amount of code, less than 70 lines of code, and 7 functions in total.
Are:
find (list, f)
Returns the first element in the list that matches fdeepCopy (obj, cache = [])
Deep copy functionforEachValue (obj, fn)
Object traversal functionisObject (obj)
Check whether it is ObjectisPromise (val)
Check if it’s a Promiseassert (condition, msg)
Assertion functionspartial (fn, arg)
Closure functions, cache functions, and arguments to run later
/**
* Get the first item that pass the test
* by second argument function
*
* @param {Array} list
* @param {Function} f
* @return {*}* /
export function find (list, f) {
return list.filter(f)[0]}/**
* Deep copy the given object considering circular structure.
* This function caches all nested objects and its copies.
* If it detects circular structure, use cached copy to avoid infinite loop.
*
* @param {*} obj
* @param {Array<Object>} cache
* @return {*}* /
export function deepCopy (obj, cache = []) {
// just return if obj is immutable value
if (obj === null || typeofobj ! = ='object') {
return obj
}
// if obj is hit, it is in circular structure
const hit = find(cache, c= > c.original === obj)
if (hit) {
return hit.copy
}
const copy = Array.isArray(obj) ? [] : {}
// put the copy into cache at first
// because we want to refer it in recursive deepCopy
cache.push({
original: obj,
copy
})
Object.keys(obj).forEach(key= > {
copy[key] = deepCopy(obj[key], cache)
})
return copy
}
/** * forEach for object */
export function forEachValue (obj, fn) {
Object.keys(obj).forEach(key= > fn(obj[key], key))
}
export function isObject (obj) {
returnobj ! = =null && typeof obj === 'object'
}
export function isPromise (val) {
return val && typeof val.then === 'function'
}
export function assert (condition, msg) {
if(! condition)throw new Error(`[vuex] ${msg}`)}export function partial (fn, arg) {
return function () {
return fn(arg)
}
}
Copy the code
Then we can find the corresponding test file, test/unit/util.spec.js, which is the test file that jest will run
Some of the functions used in common JEST tests are:
describe(name, fn)
Can be used to describe a group of similar testsit(name, fn, timeout)
Run a testexpect(value)
The Expect function is used every time a value is tested.toEqual(value)
Expect is rarely called on its own. Expect and the “matcher” functions are commonly used to assert a value. Such as:toEqual(value)
And so on..toBe(value)
Same as above.toThrowError(error?)
Same as above
Knowing the common test functions above, you can easily see the unit tests below
import { find, deepCopy, forEachValue, isObject, isPromise, assert } from '@/util'
describe('util'.() = > {
it('find: returns item when it was found'.() = > {
const list = [33.22.112.222.43]
expect(find(list, function (a) { return a % 2= = =0 })).toEqual(22)
})
it('find: returns undefined when item was not found'.() = > {
const list = [1.2.3]
expect(find(list, function (a) { return a === 9000 })).toEqual(undefined)
})
it('deepCopy: normal structure'.() = > {
const original = {
a: 1.b: 'string'.c: true.d: null.e: undefined
}
const copy = deepCopy(original)
expect(copy).toEqual(original)
})
it('deepCopy: nested structure'.() = > {
const original = {
a: {
b: 1.c: [2.3, {
d: 4}}}]const copy = deepCopy(original)
expect(copy).toEqual(original)
})
it('deepCopy: circular structure'.() = > {
const original = {
a: 1
}
original.circular = original
const copy = deepCopy(original)
expect(copy).toEqual(original)
})
it('forEachValue'.() = > {
let number = 1
function plus (value, key) {
number += value
}
const origin = {
a: 1.b: 3
}
forEachValue(origin, plus)
expect(number).toEqual(5)
})
it('isObject'.() = > {
expect(isObject(1)).toBe(false)
expect(isObject('String')).toBe(false)
expect(isObject(undefined)).toBe(false)
expect(isObject({})).toBe(true)
expect(isObject(null)).toBe(false)
expect(isObject([])).toBe(true)
expect(isObject(new Function())).toBe(false)
})
it('isPromise'.() = > {
const promise = new Promise(() = > {}, () = > {})
expect(isPromise(1)).toBe(false)
expect(isPromise(promise)).toBe(true)
expect(isPromise(new Function())).toBe(false)
})
it('assert'.() = > {
expect(assert.bind(null.false.'Hello')).toThrowError('[vuex] Hello')})})Copy the code
We can modify the above test case and rerun it to see the results
yarn test:unit
Copy the code
For example, we modified the last test case:
it('assert'.() = > {
expect(assert.bind(null.false.'H2323')).toThrowError('[vuex] Hello')})Copy the code
Output:
FAIL test/unit/util.spec.js ● Util › assert expect(received).tothrowError (expected) Expected substring: "[vuex] Hello" Received message: "[vuex] H2323"Copy the code
You can see that the test failed.
Through these test cases, we can know the function and usage of each tool function more clearly. Have a deeper understanding of code.
Learn more front-end knowledge together, wechat search [Xiaoshuai’s programming notes], updated every day