├── .nvmrc ├── .stylelintignore ├── vue.config.js ├── babel.config.js ├── src ├── store │ ├── index.js │ ├── listeners.js │ ├── utils.js │ ├── store.js │ └── store.spec.js ├── main.js ├── utils.js ├── composables │ ├── utils.js │ ├── useCoreCommonValue.js │ ├── useCoreVolatileValue.js │ ├── composableGenerator.js │ ├── index.js │ └── useCoreStoredValue.js ├── components │ ├── index.js │ ├── componentGenerator.js │ └── values.spec.js └── valueFunctions │ ├── valueFunctions.js │ └── valueFunctions.spec.js ├── commitlint.config.js ├── .editorconfig ├── .prettierrc.js ├── .gitignore ├── .stylelintrc.json ├── .npmignore ├── jest.config.js ├── README.md ├── .vscode └── settings.json ├── LICENSE ├── package.json └── .eslintrc.js /.nvmrc: -------------------------------------------------------------------------------- 1 | 12 -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | *.* 2 | !*.vue 3 | !*.scss 4 | !*.css 5 | dist -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | lintOnSave: false, 3 | } 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/app'], 3 | } 4 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Store from './store' 2 | 3 | export default Store 4 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | } 4 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | export * from './components' 2 | export * from './composables' 3 | export { default as Store } from './store' 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = tab 3 | indent_size = 4 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | max_line_length = 180 8 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | 3 | export function existsFieldInObject (obj, fieldName) { 4 | return Object.prototype.hasOwnProperty.call(obj, fieldName) 5 | } 6 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tabWidth: 4, 3 | useTabs: false, 4 | printWidth: 100, 5 | semi: false, 6 | singleQuote: true, 7 | trailingComma: 'es5', 8 | bracketSpacing: true, 9 | jsxBracketSameLine: false, 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | *.sw? 21 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "selector-pseudo-class-no-unknown": [ 4 | true, 5 | { 6 | "ignorePseudoClasses": ["global"] 7 | } 8 | ], 9 | "no-descending-specificity": null, 10 | "selector-pseudo-element-no-unknown": [ 11 | true, 12 | { 13 | "ignorePseudoElements": ["v-deep"] 14 | } 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .* 4 | *.log 5 | babel.config.js 6 | commitlint.config.js 7 | src 8 | example 9 | 10 | # local env files 11 | .env.local 12 | .env.*.local 13 | 14 | # Log files 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # Editor directories and files 20 | .idea 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /src/composables/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import { existsFieldInObject } from '../utils' 3 | 4 | export function firstDefined (defaultValue, obj, ...fieldNames) { 5 | let i = 0 6 | while (i < fieldNames.length) { 7 | if (existsFieldInObject(obj, fieldNames[i])) { 8 | return obj[fieldNames[i]] 9 | } 10 | i += 1 11 | } 12 | return defaultValue 13 | } 14 | -------------------------------------------------------------------------------- /src/composables/useCoreCommonValue.js: -------------------------------------------------------------------------------- 1 | export default function useCoreCommonValue (refValue, { disabled = false, emptyValue } = {}) { 2 | const set = (newValue) => { 3 | if (newValue !== refValue.value && !disabled) { 4 | // eslint-disable-next-line no-param-reassign 5 | refValue.value = newValue 6 | } 7 | return newValue 8 | } 9 | const clear = () => set(emptyValue) 10 | 11 | return { set, clear } 12 | } 13 | -------------------------------------------------------------------------------- /src/store/listeners.js: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from 'uuid' 2 | 3 | let listeners = [] 4 | 5 | export function addListener ({ onSetValue, onDeleteValue, onUpdateState }) { 6 | const uuid = uuidv4() 7 | listeners.push({ 8 | uuid, 9 | onSetValue, 10 | onDeleteValue, 11 | onUpdateState, 12 | }) 13 | return uuid 14 | } 15 | 16 | export function removeListener (uuid) { 17 | listeners = listeners.filter((listener) => listener.uuid !== uuid) 18 | } 19 | 20 | export function toEveryListener (action, ...args) { 21 | listeners.forEach((listener) => { 22 | listener[action]?.(...args) 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js', 'json', 'vue'], 3 | 4 | transform: { 5 | '^.+\\.vue$': 'vue-jest', 6 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 7 | '^.+\\.jsx?$': 'babel-jest', 8 | }, 9 | 10 | moduleNameMapper: { 11 | '^@/(.*)$': '/src/$1', 12 | }, 13 | 14 | snapshotSerializers: ['jest-serializer-vue'], 15 | testMatch: ['**/src/**/*.spec.(js|jsx|ts|tsx)'], 16 | testURL: 'http://localhost/', 17 | watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'], 18 | preset: '@vue/cli-plugin-unit-jest', 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-values 2 | 3 | A set of simple components to handle simple values. 4 | 5 | # Documentation 6 | 7 | Please, check the [documentation page here](https://adrianhurt.github.io/vue-values/). 8 | 9 | ## Install 10 | 11 | ``` 12 | yarn add vue-values 13 | ``` 14 | or 15 | ``` 16 | npm install vue-values --save 17 | ``` 18 | 19 | And that's all! Here you have a simple usage example. 20 | 21 | ```vue 22 | 27 | 28 | 36 | ``` -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.format.enable": false, 3 | "files.associations": { 4 | "*.html": "html" 5 | }, 6 | "trailing-spaces.trimOnSave": true, 7 | "editor.formatOnSave": false, 8 | "editor.formatOnPaste": false, 9 | "prettier.singleQuote": true, 10 | "prettier.eslintIntegration": true, 11 | "prettier.tabWidth": 4, 12 | "prettier.printWidth": 100, 13 | "editor.rulers": [180], 14 | "javascript.updateImportsOnFileMove.enabled": "always", 15 | "typescript.check.npmIsInstalled": false, 16 | "eslint.packageManager": "yarn", 17 | "eslint.options": { 18 | "extensions": [".html", ".js", ".vue", ".jsx"] 19 | }, 20 | "eslint.validate": ["javascript", "javascriptreact", "html", "vue"], 21 | "editor.codeActionsOnSave": { 22 | "source.fixAll.eslint": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Adrian Hurtado 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/composables/useCoreVolatileValue.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import { ref } from 'vue' 3 | import useCoreCommonValue from './useCoreCommonValue' 4 | import { firstDefined } from './utils' 5 | 6 | export function useCoreSimpleVolatileValue (emptyValue, options = {}) { 7 | const initialOrDefaultValue = firstDefined(emptyValue, options, 'initialValue', 'defaultValue') 8 | return ref(initialOrDefaultValue) 9 | } 10 | 11 | export default function useCoreVolatileValue (emptyValue, options = {}) { 12 | const { disabled = false } = options 13 | 14 | const defaultValue = firstDefined(emptyValue, options, 'defaultValue') 15 | const initialValue = firstDefined(emptyValue, options, 'initialValue') 16 | const defaultOrInitialValue = firstDefined(emptyValue, options, 'defaultValue', 'initialValue') 17 | 18 | const refValue = useCoreSimpleVolatileValue(emptyValue, options) 19 | 20 | const { set, clear } = useCoreCommonValue(refValue, { disabled, emptyValue }) 21 | 22 | return { 23 | value: refValue, 24 | set, 25 | clear, 26 | resetToDefault: () => set(defaultValue), 27 | resetToInitial: () => set(initialValue), 28 | reset: () => set(defaultOrInitialValue), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/composables/composableGenerator.js: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue' 2 | import useCoreVolatileValue from './useCoreVolatileValue' 3 | import useCoreStoredValue from './useCoreStoredValue' 4 | 5 | function customize ({ 6 | useCoreValueFn, 7 | options = {}, 8 | emptyValue = undefined, 9 | customSetter = undefined, 10 | customMutator = {}, 11 | customFunction = {}, 12 | customComputed = {}, 13 | }) { 14 | const valueArgs = useCoreValueFn(emptyValue, options) 15 | if (customSetter) { 16 | const { set: originalSet } = valueArgs 17 | valueArgs.set = (newValue) => originalSet(customSetter(options)(newValue)) 18 | } 19 | Object.keys(customFunction).forEach((key) => { 20 | valueArgs[key] = (...args) => customFunction[key](valueArgs.value.value, options)(...args) 21 | }) 22 | Object.keys(customMutator).forEach((key) => { 23 | valueArgs[key] = (...args) => valueArgs.set(customMutator[key](valueArgs.value.value, options)(...args)) 24 | }) 25 | Object.keys(customComputed).forEach((key) => { 26 | valueArgs[key] = computed(() => customComputed[key](valueArgs.value.value, options)) 27 | }) 28 | return valueArgs 29 | } 30 | 31 | export function volatileComposableGenerator (composableOptions = {}) { 32 | return (options = {}) => customize({ useCoreValueFn: useCoreVolatileValue, options, ...composableOptions }) 33 | } 34 | export function storedComposableGenerator (composableOptions = {}) { 35 | return (uid, options = {}) => customize({ useCoreValueFn: (...args) => useCoreStoredValue(uid, ...args), options, ...composableOptions }) 36 | } 37 | 38 | export function bothComposableGenerator (composableOptions = {}) { 39 | return { 40 | volatile: volatileComposableGenerator(composableOptions), 41 | stored: storedComposableGenerator(composableOptions), 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/composables/index.js: -------------------------------------------------------------------------------- 1 | import ValueFunctions from '../valueFunctions/valueFunctions' 2 | import { useCoreSimpleVolatileValue } from './useCoreVolatileValue' 3 | import { useCoreSimpleStoredValue } from './useCoreStoredValue' 4 | import { bothComposableGenerator } from './composableGenerator' 5 | 6 | export const useValue = (options = {}) => useCoreSimpleVolatileValue(undefined, options) 7 | export const useStoredValue = (uid, options = {}) => useCoreSimpleStoredValue(uid, undefined, options) 8 | 9 | export const { volatile: useVueValue, stored: useVueStoredValue } = bothComposableGenerator() 10 | 11 | export const { volatile: useVueBoolean, stored: useVueStoredBoolean } = bothComposableGenerator({ 12 | emptyValue: false, 13 | customMutator: ValueFunctions.boolean.mutator, 14 | customComputed: ValueFunctions.boolean.computed, 15 | }) 16 | 17 | export const { volatile: useVueNumber, stored: useVueStoredNumber } = bothComposableGenerator({ 18 | customSetter: ValueFunctions.number.setter, 19 | customMutator: ValueFunctions.number.mutator, 20 | customComputed: ValueFunctions.number.computed, 21 | }) 22 | 23 | export const { volatile: useVueString, stored: useVueStoredString } = bothComposableGenerator({ 24 | customMutator: ValueFunctions.string.mutator, 25 | customFunction: ValueFunctions.string.function, 26 | }) 27 | 28 | export const { volatile: useVueArray, stored: useVueStoredArray } = bothComposableGenerator({ 29 | emptyValue: [], 30 | customMutator: ValueFunctions.array.mutator, 31 | customFunction: ValueFunctions.array.function, 32 | }) 33 | 34 | export const { volatile: useVueObject, stored: useVueStoredObject } = bothComposableGenerator({ 35 | emptyValue: {}, 36 | customMutator: ValueFunctions.object.mutator, 37 | }) 38 | 39 | export const { volatile: useVueSet, stored: useVueStoredSet } = bothComposableGenerator({ 40 | emptyValue: new Set(), 41 | customMutator: ValueFunctions.set.mutator, 42 | }) 43 | 44 | export const { volatile: useVueMap, stored: useVueStoredMap } = bothComposableGenerator({ 45 | emptyValue: new Map(), 46 | customMutator: ValueFunctions.map.mutator, 47 | }) 48 | -------------------------------------------------------------------------------- /src/store/utils.js: -------------------------------------------------------------------------------- 1 | import { existsFieldInObject } from '../utils' 2 | 3 | export function isObject (obj) { 4 | return typeof obj === 'object' && obj !== null 5 | } 6 | 7 | export function existsPathInObject (obj, path) { 8 | const section = path.split('.') 9 | let node = obj 10 | for (let i = 0; i < section.length; i += 1) { 11 | if (!existsFieldInObject(node, section[i])) { 12 | return false 13 | } 14 | node = node[section[i]] 15 | } 16 | return true 17 | } 18 | 19 | function getParentNodeAndLastSection (uid, obj) { 20 | const sections = uid.split('.') 21 | let node = obj 22 | for (let i = 0; i < sections.length - 1; i += 1) { 23 | if (!existsFieldInObject(node, sections[i])) { 24 | node[sections[i]] = {} 25 | } 26 | node = node[sections[i]] 27 | } 28 | return [node, sections[sections.length - 1]] 29 | } 30 | 31 | export function getValueFromObject (uid, obj, { defaultValue = undefined } = {}) { 32 | const [parent, lastSection] = getParentNodeAndLastSection(uid, obj) 33 | if (!existsFieldInObject(parent, lastSection)) { 34 | parent[lastSection] = defaultValue 35 | } 36 | return parent[lastSection] 37 | } 38 | export function setValueIntoObject (uid, obj, newValue) { 39 | const [parent, lastSection] = getParentNodeAndLastSection(uid, obj) 40 | parent[lastSection] = newValue 41 | return newValue 42 | } 43 | export function removeValueFromObject (uid, obj) { 44 | const [parent, lastSection] = getParentNodeAndLastSection(uid, obj) 45 | delete parent[lastSection] 46 | return obj 47 | } 48 | 49 | export function deepMerge (target, source) { 50 | Object.keys(source).forEach((key) => { 51 | if (isObject(source[key])) { 52 | if (!existsFieldInObject(target, key)) { 53 | // eslint-disable-next-line no-param-reassign 54 | target[key] = {} 55 | } 56 | deepMerge(target[key], source[key]) 57 | } else { 58 | // eslint-disable-next-line no-param-reassign 59 | target[key] = source[key] 60 | } 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /src/composables/useCoreStoredValue.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import { customRef } from 'vue' 3 | import useCoreCommonValue from './useCoreCommonValue' 4 | import Store from '../store/store' 5 | import { firstDefined } from './utils' 6 | import { existsFieldInObject } from '../utils' 7 | 8 | export function useCoreSimpleStoredValue (uid, emptyValue, options = {}) { 9 | const initialOrDefaultValue = firstDefined(emptyValue, options, 'initialValue', 'defaultValue') 10 | return customRef((track, trigger) => ({ 11 | get () { 12 | const v = Store.value(uid).get(initialOrDefaultValue) 13 | track() 14 | return v 15 | }, 16 | set (newValue) { 17 | Store.value(uid).set(newValue) 18 | trigger() 19 | }, 20 | })) 21 | } 22 | 23 | export default function useCoreStoredValue (uid, emptyValue, options = {}) { 24 | const { disabled = false } = options 25 | 26 | const refValue = useCoreSimpleStoredValue(uid, emptyValue, options) 27 | 28 | const { set, clear } = useCoreCommonValue(refValue, { disabled, emptyValue }) 29 | 30 | const resetToDefault = () => { 31 | if (existsFieldInObject(options, 'defaultValue')) { 32 | set(options.defaultValue) 33 | } else { 34 | Store.value(uid).resetToDefault(emptyValue) 35 | } 36 | } 37 | const resetToInitial = () => { 38 | if (existsFieldInObject(options, 'initialValue')) { 39 | set(options.initialValue) 40 | } else { 41 | Store.value(uid).resetToInitial(emptyValue) 42 | } 43 | } 44 | const reset = () => { 45 | if (existsFieldInObject(options, 'defaultValue')) { 46 | set(options.defaultValue) 47 | } else if (existsFieldInObject(options, 'initialValue')) { 48 | set(options.initialValue) 49 | } else { 50 | Store.value(uid).reset(emptyValue) 51 | } 52 | } 53 | 54 | return { 55 | value: refValue, 56 | set, 57 | clear, 58 | resetToDefault, 59 | resetToInitial, 60 | reset, 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import ValueFunctions from '../valueFunctions/valueFunctions' 2 | import { bothComponentGenerator } from './componentGenerator' 3 | 4 | export const { volatile: Value, stored: StoredValue } = bothComponentGenerator({ 5 | name: 'Value', 6 | valueType: undefined, 7 | emptyValue: undefined, 8 | customSetter: undefined, 9 | customMutator: {}, 10 | customFunction: {}, 11 | customComputed: {}, 12 | extraProps: {}, 13 | extraEmits: [], 14 | }) 15 | 16 | export const { volatile: BooleanValue, stored: StoredBooleanValue } = bothComponentGenerator({ 17 | name: 'BooleanValue', 18 | valueType: Boolean, 19 | emptyValue: false, 20 | customMutator: ValueFunctions.boolean.mutator, 21 | customComputed: ValueFunctions.boolean.computed, 22 | }) 23 | 24 | export const { volatile: NumberValue, stored: StoredNumberValue } = bothComponentGenerator({ 25 | name: 'NumberValue', 26 | valueType: Number, 27 | emptyValue: undefined, 28 | customSetter: ValueFunctions.number.setter, 29 | customMutator: ValueFunctions.number.mutator, 30 | customComputed: ValueFunctions.number.computed, 31 | extraProps: { 32 | min: Number, 33 | max: Number, 34 | }, 35 | }) 36 | 37 | export const { volatile: StringValue, stored: StoredStringValue } = bothComponentGenerator({ 38 | name: 'StringValue', 39 | valueType: String, 40 | emptyValue: undefined, 41 | customMutator: ValueFunctions.string.mutator, 42 | customFunction: ValueFunctions.string.function, 43 | }) 44 | 45 | export const { volatile: ArrayValue, stored: StoredArrayValue } = bothComponentGenerator({ 46 | name: 'ArrayValue', 47 | valueType: Array, 48 | emptyValue: [], 49 | customMutator: ValueFunctions.array.mutator, 50 | customFunction: ValueFunctions.array.function, 51 | }) 52 | 53 | export const { volatile: ObjectValue, stored: StoredObjectValue } = bothComponentGenerator({ 54 | name: 'ObjectValue', 55 | valueType: Object, 56 | emptyValue: {}, 57 | customMutator: ValueFunctions.object.mutator, 58 | }) 59 | 60 | export const { volatile: SetValue, stored: StoredSetValue } = bothComponentGenerator({ 61 | name: 'SetValue', 62 | valueType: Set, 63 | emptyValue: new Set(), 64 | customMutator: ValueFunctions.set.mutator, 65 | }) 66 | 67 | export const { volatile: MapValue, stored: StoredMapValue } = bothComponentGenerator({ 68 | name: 'MapValue', 69 | valueType: Map, 70 | emptyValue: new Map(), 71 | customMutator: ValueFunctions.map.mutator, 72 | }) 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-values", 3 | "version": "1.0.0-alpha.2", 4 | "description": "A set of simple composable functions and components to handle simple values.", 5 | "author": "adrianhurt", 6 | "license": "MIT", 7 | "keywords": [ 8 | "vue", 9 | "vuejs", 10 | "value", 11 | "values", 12 | "use", 13 | "composables", 14 | "hooks", 15 | "handler", 16 | "store", 17 | "stored", 18 | "synchronized" 19 | ], 20 | "homepage": "https://github.com/adrianhurt/vue-values", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/adrianhurt/vue-values" 24 | }, 25 | "main": "./dist/vue-values.common.js", 26 | "scripts": { 27 | "clean": "rm -rf dist", 28 | "clearAll": "yarn clean && rm -rf node_modules", 29 | "reinstall": "yarn clearAll && yarn install", 30 | "test:unit": "vue-cli-service test:unit", 31 | "test:clear": "vue-cli-service test:unit --clearCache", 32 | "lint": "vue-cli-service lint", 33 | "validate": "yarn test:unit && yarn lint", 34 | "prebuild": "yarn clean", 35 | "build": "vue-cli-service build --target lib --name vue-values src/main.js", 36 | "dist": "yarn validate && yarn build", 37 | "prepublishOnly": "yarn dist" 38 | }, 39 | "gitHooks": { 40 | "commit-msg": "commitlint -e -V", 41 | "pre-commit": "yarn validate" 42 | }, 43 | "files": [ 44 | "dist" 45 | ], 46 | "peerDependencies": { 47 | "core-js": "^3.12.0", 48 | "uuid": "^8.0.0", 49 | "vue": "^3.0.11" 50 | }, 51 | "devDependencies": { 52 | "core-js": "^3.12.0", 53 | "uuid": "^8.3.2", 54 | "vue": "^3.0.11", 55 | "@commitlint/cli": "^8.3.5", 56 | "@commitlint/config-conventional": "^8.3.4", 57 | "@vue/babel-preset-app": "^4.5.12", 58 | "@vue/cli-plugin-babel": "^4.5.12", 59 | "@vue/cli-plugin-eslint": "^4.5.12", 60 | "@vue/cli-plugin-unit-jest": "^4.5.12", 61 | "@vue/cli-service": "^4.5.12", 62 | "@vue/compiler-sfc": "^3.0.11", 63 | "@vue/eslint-config-airbnb": "^5.3.0", 64 | "@vue/eslint-config-standard": "^5.1.2", 65 | "@vue/test-utils": "^2.0.0-rc.6", 66 | "babel-eslint": "^10.1.0", 67 | "eslint": "^7.25.0", 68 | "eslint-config-airbnb-base": "^14.2.1", 69 | "eslint-plugin-babel": "^5.3.1", 70 | "eslint-plugin-vue": "^7.9.0", 71 | "node-sass": "^4.14.1", 72 | "sass-loader": "^8.0.2", 73 | "vue-jest": "^5.0.0-alpha.8" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/components/componentGenerator.js: -------------------------------------------------------------------------------- 1 | import { watch } from 'vue' 2 | import { volatileComposableGenerator, storedComposableGenerator } from '../composables/composableGenerator' 3 | 4 | export function singleComponentGenerator ({ 5 | name, 6 | useValueFn, 7 | valueType, 8 | customComputed = {}, 9 | extraProps = {}, 10 | extraEmits = [], 11 | }) { 12 | return { 13 | name, 14 | props: { 15 | defaultValue: { 16 | type: valueType, 17 | }, 18 | initialValue: { 19 | type: valueType, 20 | }, 21 | disabled: { 22 | type: Boolean, 23 | default: false, 24 | }, 25 | ...extraProps, 26 | }, 27 | emits: [ 28 | 'change', 29 | ...extraEmits, 30 | ], 31 | setup (props, context) { 32 | const valueArgs = useValueFn(props) 33 | const { value, ...valueFunctionsOrComputed } = valueArgs 34 | 35 | watch(() => value.value, (newValue, oldValue) => { 36 | context.emit('change', newValue, oldValue) 37 | }) 38 | 39 | return () => { 40 | const scoped = { value: value.value } 41 | Object.keys(valueFunctionsOrComputed).forEach((key) => { 42 | if (customComputed[key]) { 43 | scoped[key] = valueFunctionsOrComputed[key].value 44 | } else { 45 | scoped[key] = valueFunctionsOrComputed[key] 46 | } 47 | }) 48 | return context.slots.default(scoped) 49 | } 50 | }, 51 | } 52 | } 53 | export function volatileComponentGenerator (componentOptions) { 54 | const useValueFn = volatileComposableGenerator(componentOptions) 55 | return singleComponentGenerator({ 56 | useValueFn, 57 | ...componentOptions, 58 | }) 59 | } 60 | export function storedComponentGenerator (componentOptions) { 61 | const useValueFn = storedComposableGenerator(componentOptions) 62 | return singleComponentGenerator({ 63 | useValueFn: (props) => useValueFn(props.uid, props), 64 | ...componentOptions, 65 | extraProps: { 66 | ...(componentOptions.extraProps || {}), 67 | uid: { 68 | type: String, 69 | required: true, 70 | }, 71 | }, 72 | }) 73 | } 74 | 75 | export function bothComponentGenerator (componentOptions) { 76 | const volatile = volatileComponentGenerator(componentOptions) 77 | const stored = storedComponentGenerator({ name: `Stored${componentOptions.name}`, ...componentOptions }) 78 | return { volatile, stored } 79 | } 80 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es6: true, 6 | node: true, 7 | }, 8 | 9 | extends: ['eslint:recommended', 'airbnb-base', 'plugin:vue/vue3-recommended'], 10 | plugins: ['babel'], 11 | 12 | globals: { 13 | Atomics: 'readonly', 14 | SharedArrayBuffer: 'readonly', 15 | }, 16 | 17 | parserOptions: { 18 | parser: 'babel-eslint', 19 | ecmaVersion: 2019, 20 | sourceType: 'module', 21 | }, 22 | 23 | rules: { 24 | 'no-unused-expressions': 'off', 25 | 'babel/no-unused-expressions': 'error', 26 | 'no-param-reassign': [ 27 | 'error', 28 | { props: true, ignorePropertyModificationsFor: ['state', 'el'] }, 29 | ], 30 | 'space-before-function-paren': ['error', 'always'], 31 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 32 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 33 | 'import/no-extraneous-dependencies': 'off', 34 | 'linebreak-style': 'off', 35 | 'no-tabs': 'off', 36 | 'vue/attribute-hyphenation': [ 37 | 2, 38 | 'never', 39 | { 40 | ignore: [ 41 | 'stroke-width', 42 | 'font-size', 43 | 'text-anchor', 44 | 'stroke-dasharray', 45 | 'stop-color', 46 | 'stop-opacity', 47 | ], 48 | }, 49 | ], 50 | 'vue/html-closing-bracket-newline': [ 51 | 'error', 52 | { 53 | singleline: 'never', 54 | multiline: 'always', 55 | }, 56 | ], 57 | 'vue/html-indent': [ 58 | 'error', 59 | 4, 60 | { 61 | attribute: 1, 62 | closeBracket: 0, 63 | alignAttributesVertically: true, 64 | ignores: [], 65 | }, 66 | ], 67 | 'object-curly-spacing': ['error', 'always'], 68 | semi: ['error', 'never'], 69 | 'max-len': [ 70 | 'error', 71 | { 72 | comments: 180, 73 | code: 180, 74 | }, 75 | ], 76 | indent: [ 77 | 'error', 78 | 4, 79 | { 80 | SwitchCase: 1, 81 | VariableDeclarator: 1, 82 | outerIIFEBody: 1, 83 | }, 84 | ], 85 | 'comma-dangle': ['error', 'always-multiline'], 86 | 'vue/html-closing-bracket-spacing': 'error', 87 | 'vue/prop-name-casing': 'error', 88 | }, 89 | 90 | overrides: [ 91 | { 92 | files: ['**/*.spec.js'], 93 | rules: { 'import/no-extraneous-dependencies': 'off', 'no-debugger': 'off', 'no-console': 'off' }, 94 | env: { jest: true }, 95 | }, 96 | ], 97 | } 98 | -------------------------------------------------------------------------------- /src/valueFunctions/valueFunctions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * For each type of value we have: 3 | * - setter: (options) => newValue => newCuratedValue 4 | * - mutator: (value, options) => (...args) => newValue 5 | * - function: (value, options) => (...args) => anotherValue 6 | * - computed: (value, options) => computedValue 7 | */ 8 | 9 | export default { 10 | boolean: { 11 | mutator: { 12 | toggle: (bool) => () => !bool, 13 | }, 14 | computed: { 15 | negative: (bool) => () => !bool, 16 | }, 17 | }, 18 | number: { 19 | setter: ({ min = -Infinity, max = Infinity } = {}) => (newVal) => Math.max(min, Math.min(max, newVal)), 20 | mutator: { 21 | increment: (num, { max = Infinity } = {}) => (delta = 1, m = Infinity) => Math.min(max, m, num + delta), 22 | decrement: (num, { min = -Infinity } = {}) => (delta = 1, m = -Infinity) => Math.max(min, m, num - delta), 23 | }, 24 | computed: { 25 | isFirst: (num, { min = -Infinity } = {}) => num === min, 26 | isLast: (num, { max = -Infinity } = {}) => num === max, 27 | }, 28 | }, 29 | string: { 30 | mutator: { 31 | append: (string) => (str) => string + str, 32 | prepend: (string) => (str) => str + string, 33 | insert: (string) => (index, str) => string.slice(0, index) + str + string.slice(index), 34 | replace: (string) => (...args) => string.replace(...args), 35 | substring: (string) => (...args) => string.substring(...args), 36 | }, 37 | function: { 38 | substring: (string) => (...args) => string.substring(...args), 39 | }, 40 | }, 41 | array: { 42 | mutator: { 43 | first: (array) => () => array[0], 44 | last: (array) => () => array[array.length - 1], 45 | append: (array) => (...items) => array.concat(...items), 46 | prepend: (array) => (...items) => [...items, ...array], 47 | insert: (array) => (index, ...items) => [...array.slice(0, index), ...items, ...array.slice(index)], 48 | removeFirst: (array) => () => array.slice(1), 49 | removeLast: (array) => () => array.slice(0, -1), 50 | removeIndex: (array) => (index) => array.filter((_, i) => i !== index), 51 | remove: (array) => (...items) => array.filter((i) => !items.includes(i)), 52 | splice: (array) => (...args) => { 53 | const copy = [...array] 54 | copy.splice(...args) 55 | return copy 56 | }, 57 | reverse: (array) => () => [...array].reverse(), 58 | sort: (array) => (comparator) => [...array].sort(comparator), 59 | }, 60 | function: { 61 | first: (array) => () => array[0], 62 | last: (array) => () => array[array.length - 1], 63 | slice: (array) => (...args) => array.slice(...args), 64 | }, 65 | }, 66 | object: { 67 | mutator: { 68 | setValue: (obj) => (key, value) => ({ ...obj, [key]: value }), 69 | setValues: (obj) => (other) => ({ ...obj, ...other }), 70 | remove: (obj) => (...keys) => { 71 | const copy = { ...obj } 72 | keys.forEach((key) => (delete copy[key])) 73 | return copy 74 | }, 75 | }, 76 | }, 77 | set: { 78 | mutator: { 79 | add: (set) => (...items) => (new Set([...set, ...items])), 80 | remove: (set) => (...items) => { 81 | const copy = new Set([...set]) 82 | items.forEach((item) => (copy.delete(item))) 83 | return copy 84 | }, 85 | toggle: (set) => (...items) => { 86 | const copy = new Set([...set]) 87 | items.forEach((item) => { 88 | if (copy.has(item)) { 89 | copy.delete(item) 90 | } else { 91 | copy.add(item) 92 | } 93 | }) 94 | return copy 95 | }, 96 | }, 97 | }, 98 | map: { 99 | mutator: { 100 | setValue: (map) => (key, value) => { 101 | const copy = new Map(map) 102 | copy.set(key, value) 103 | return copy 104 | }, 105 | setValues: (map) => (keysAndvalues) => { 106 | const copy = new Map(map) 107 | keysAndvalues.forEach(([key, value]) => (copy.set(key, value))) 108 | return copy 109 | }, 110 | remove: (map) => (...keys) => { 111 | const copy = new Map(map) 112 | keys.forEach((key) => (copy.delete(key))) 113 | return copy 114 | }, 115 | }, 116 | }, 117 | } 118 | -------------------------------------------------------------------------------- /src/store/store.js: -------------------------------------------------------------------------------- 1 | import { reactive } from 'vue' 2 | import { addListener, removeListener, toEveryListener } from './listeners' 3 | import ValueFunctions from '../valueFunctions/valueFunctions' 4 | import { 5 | existsPathInObject, 6 | getValueFromObject, 7 | setValueIntoObject, 8 | removeValueFromObject, 9 | deepMerge, 10 | } from './utils' 11 | 12 | const state = reactive({}) 13 | 14 | let defaultState = {} 15 | let initialState = {} 16 | 17 | /** 18 | * Deletes a stored value 19 | */ 20 | function remove (uid, { notify = true } = {}) { 21 | const result = removeValueFromObject(uid, state) 22 | if (notify) { 23 | toEveryListener('onDeleteValue', uid, state) 24 | toEveryListener('onUpdateState', state) 25 | } 26 | return result 27 | } 28 | /** 29 | * Deletes every stored value 30 | */ 31 | function removeAll ({ notify = true } = {}) { 32 | Object.keys(state).forEach((key) => { 33 | remove(key, { notify: false }) 34 | }) 35 | if (notify) toEveryListener('onUpdateState', state) 36 | } 37 | 38 | /** 39 | * Merges an object to the current state 40 | */ 41 | function mergeState (newState, { notify = true } = {}) { 42 | deepMerge(state, newState) 43 | if (notify) toEveryListener('onUpdateState', state) 44 | return state 45 | } 46 | 47 | /** 48 | * Sets every value found within newState 49 | */ 50 | function setState (newState, { notify = true } = {}) { 51 | removeAll({ notify: false }) 52 | mergeState(newState, { notify: false }) 53 | if (notify) toEveryListener('onUpdateState', state) 54 | } 55 | 56 | /** 57 | * Setter for a stored value 58 | */ 59 | function set (uid, newValue, { notify = true } = {}) { 60 | const result = setValueIntoObject(uid, state, newValue) 61 | if (notify) { 62 | toEveryListener('onSetValue', uid, newValue, state) 63 | toEveryListener('onUpdateState', state) 64 | } 65 | return result 66 | } 67 | 68 | /** 69 | * Getter for a stored value 70 | */ 71 | function get (uid, defaultValue) { 72 | return getValueFromObject(uid, state, { defaultValue }) 73 | } 74 | 75 | // ////////////////////////// 76 | // Default values 77 | 78 | function setDefaultState (newDefaultState) { 79 | defaultState = newDefaultState || {} 80 | } 81 | 82 | function setDefaultValue (uid, newValue) { 83 | return setValueIntoObject(uid, defaultState, newValue) 84 | } 85 | function getDefaultValue (uid, defaultValue) { 86 | return getValueFromObject(uid, defaultState, { defaultValue }) 87 | } 88 | 89 | function removeDefaultValue (uid) { 90 | return removeValueFromObject(uid, defaultState) 91 | } 92 | 93 | function resetToDefault (uid, defaultValue) { 94 | set(uid, getDefaultValue(uid, defaultValue)) 95 | } 96 | 97 | function resetAllToDefault () { 98 | mergeState(defaultState) 99 | } 100 | 101 | // ////////////////////////// 102 | // Initial values 103 | 104 | function setInitialState (newInitialState) { 105 | initialState = newInitialState || {} 106 | } 107 | 108 | function setInitialValue (uid, newValue) { 109 | return setValueIntoObject(uid, initialState, newValue) 110 | } 111 | function getInitialValue (uid, defaultValue) { 112 | return getValueFromObject(uid, initialState, { defaultValue }) 113 | } 114 | 115 | function removeInitialValue (uid) { 116 | return removeValueFromObject(uid, initialState) 117 | } 118 | 119 | function resetToInitial (uid, defaultValue) { 120 | set(uid, getInitialValue(uid, defaultValue)) 121 | } 122 | 123 | function resetAllToInitial () { 124 | mergeState(initialState) 125 | } 126 | 127 | // ////////////////////////// 128 | // General reset (default or initial) 129 | function reset (uid, defaultValue) { 130 | if (existsPathInObject(defaultState, uid)) { 131 | set(uid, getDefaultValue(uid)) 132 | } else if (existsPathInObject(initialState, uid)) { 133 | set(uid, getInitialValue(uid)) 134 | } else { 135 | set(uid, defaultValue) 136 | } 137 | } 138 | 139 | // ////////////////////////// 140 | // Auxiliar functions 141 | 142 | function valueAs (type) { 143 | return (uid) => { 144 | const applyToUid = (fn) => (...args) => fn(uid, ...args) 145 | 146 | const setter = type && type !== 'value' && ValueFunctions[type].setter 147 | ? (newValue, options) => applyToUid(set)(ValueFunctions[type].setter(options)(newValue)) 148 | : applyToUid(set) 149 | 150 | const customFunction = (type && type !== 'value' && ValueFunctions[type].function) || {} 151 | const customMutator = (type && type !== 'value' && ValueFunctions[type].mutator) || {} 152 | return { 153 | get: applyToUid(get), 154 | set: setter, 155 | resetToDefault: applyToUid(resetToDefault), 156 | resetToInitial: applyToUid(resetToInitial), 157 | reset: applyToUid(reset), 158 | remove: applyToUid(remove), 159 | ...Object.keys(customFunction).reduce( 160 | (acc, key) => ({ 161 | ...acc, 162 | [key]: (...args) => customFunction[key](get(uid))(...args), 163 | }), 164 | {}, 165 | ), 166 | ...Object.keys(customMutator).reduce( 167 | (acc, key) => ({ 168 | ...acc, 169 | [key]: (...args) => setter(customMutator[key](get(uid))(...args)), 170 | }), 171 | {}, 172 | ), 173 | } 174 | } 175 | } 176 | 177 | export default { 178 | state, 179 | getValue: get, 180 | setValue: set, 181 | resetToDefault, 182 | resetToInitial, 183 | reset, 184 | remove, 185 | mergeState, 186 | setState, 187 | setDefaultState, 188 | setDefaultValue, 189 | getDefaultValue, 190 | removeDefaultValue, 191 | resetAllToDefault, 192 | setInitialState, 193 | setInitialValue, 194 | getInitialValue, 195 | removeInitialValue, 196 | resetAllToInitial, 197 | removeAll, 198 | addListener, 199 | removeListener, 200 | value: valueAs('value'), 201 | boolean: valueAs('boolean'), 202 | number: valueAs('number'), 203 | string: valueAs('string'), 204 | array: valueAs('array'), 205 | object: valueAs('object'), 206 | set: valueAs('set'), 207 | map: valueAs('map'), 208 | } 209 | -------------------------------------------------------------------------------- /src/store/store.spec.js: -------------------------------------------------------------------------------- 1 | import Store from './store' 2 | 3 | beforeEach(() => { 4 | Store.removeAll() 5 | Store.setDefaultState() 6 | Store.setInitialState() 7 | }) 8 | 9 | describe('store', () => { 10 | it('value.set', () => { 11 | expect(Store.state.myValue).toBe(undefined) 12 | Store.value('myValue').set('foo') 13 | expect(Store.state.myValue).toBe('foo') 14 | }) 15 | it('value.get', () => { 16 | expect(Store.value('myValue').get()).toBe(undefined) 17 | Store.value('myValue').set('foo') 18 | expect(Store.value('myValue').get()).toBe('foo') 19 | }) 20 | it('value.get: set it if not exist with a default value', () => { 21 | expect(Store.state.myValue).toBe(undefined) 22 | expect(Store.value('myValue').get('foo')).toBe('foo') 23 | expect(Store.state.myValue).toBe('foo') 24 | }) 25 | it('value.remove', () => { 26 | Store.value('myValue').set('foo') 27 | expect(Store.value('myValue').get()).toBe('foo') 28 | Store.value('myValue').remove() 29 | expect(Store.value('myValue').get()).toBe(undefined) 30 | }) 31 | it('setState', () => { 32 | expect(Store.state.myValue1).toBe(undefined) 33 | expect(Store.state.myValue2).toBe(undefined) 34 | Store.setState({ myValue1: 'foo', myValue2: 'bar' }) 35 | expect(Store.state.myValue1).toBe('foo') 36 | expect(Store.state.myValue2).toBe('bar') 37 | }) 38 | it('removeAll', () => { 39 | Store.setState({ myValue1: 'foo', myValue2: 'bar' }) 40 | expect(Store.state.myValue1).toBe('foo') 41 | expect(Store.state.myValue2).toBe('bar') 42 | Store.removeAll() 43 | expect(Store.state.myValue1).toBe(undefined) 44 | expect(Store.state.myValue2).toBe(undefined) 45 | }) 46 | 47 | it('setDefaultValue and getDefaultValue', () => { 48 | expect(Store.getDefaultValue('myValue')).toBe(undefined) 49 | Store.setDefaultValue('myValue', 'foo') 50 | expect(Store.getDefaultValue('myValue')).toBe('foo') 51 | Store.setDefaultValue('myValue', 'bar') 52 | expect(Store.getDefaultValue('myValue')).toBe('bar') 53 | }) 54 | it('setDefaultState', () => { 55 | expect(Store.getDefaultValue('myValue1')).toBe(undefined) 56 | expect(Store.getDefaultValue('myValue2')).toBe(undefined) 57 | Store.setDefaultState({ myValue1: 'foo', myValue2: 'bar' }) 58 | expect(Store.getDefaultValue('myValue1')).toBe('foo') 59 | expect(Store.getDefaultValue('myValue2')).toBe('bar') 60 | }) 61 | it('removeDefaultValue', () => { 62 | Store.setDefaultValue('myValue', 'foo') 63 | expect(Store.getDefaultValue('myValue')).toBe('foo') 64 | Store.removeDefaultValue('myValue') 65 | expect(Store.getDefaultValue('myValue')).toBe(undefined) 66 | }) 67 | 68 | it('setInitialValue and getInitialValue', () => { 69 | expect(Store.getInitialValue('myValue')).toBe(undefined) 70 | Store.setInitialValue('myValue', 'foo') 71 | expect(Store.getInitialValue('myValue')).toBe('foo') 72 | Store.setInitialValue('myValue', 'bar') 73 | expect(Store.getInitialValue('myValue')).toBe('bar') 74 | }) 75 | it('setInitialState', () => { 76 | expect(Store.getInitialValue('myValue1')).toBe(undefined) 77 | expect(Store.getInitialValue('myValue2')).toBe(undefined) 78 | Store.setInitialState({ myValue1: 'foo', myValue2: 'bar' }) 79 | expect(Store.getInitialValue('myValue1')).toBe('foo') 80 | expect(Store.getInitialValue('myValue2')).toBe('bar') 81 | }) 82 | it('removeInitialValue', () => { 83 | Store.setInitialValue('myValue', 'foo') 84 | expect(Store.getInitialValue('myValue')).toBe('foo') 85 | Store.removeInitialValue('myValue') 86 | expect(Store.getInitialValue('myValue')).toBe(undefined) 87 | }) 88 | 89 | it('value.resetToDefault', () => { 90 | Store.setDefaultValue('myValue', 'foo') 91 | expect(Store.state.myValue).toBe(undefined) 92 | Store.value('myValue').resetToDefault() 93 | expect(Store.state.myValue).toBe('foo') 94 | }) 95 | it('value.resetToInitial', () => { 96 | Store.setInitialValue('myValue', 'foo') 97 | expect(Store.state.myValue).toBe(undefined) 98 | Store.value('myValue').resetToInitial() 99 | expect(Store.state.myValue).toBe('foo') 100 | }) 101 | it('value.reset (default priors to initial)', () => { 102 | Store.setDefaultValue('myValue', 'foo') 103 | Store.setInitialValue('myValue', 'bar') 104 | expect(Store.state.myValue).toBe(undefined) 105 | Store.value('myValue').reset() 106 | expect(Store.state.myValue).toBe('foo') 107 | }) 108 | it('value.reset (initial if default is not present)', () => { 109 | Store.setInitialValue('myValue', 'bar') 110 | expect(Store.state.myValue).toBe(undefined) 111 | Store.value('myValue').reset() 112 | expect(Store.state.myValue).toBe('bar') 113 | }) 114 | it('value.reset (undefined if default nor initial are present)', () => { 115 | Store.value('myValue').set('foo') 116 | expect(Store.state.myValue).toBe('foo') 117 | Store.value('myValue').reset() 118 | expect(Store.state.myValue).toBe(undefined) 119 | }) 120 | 121 | it('resetAllToDefault', () => { 122 | Store.setDefaultState({ myValue1: 'foo', myValue2: 'bar' }) 123 | expect(Store.state.myValue1).toBe(undefined) 124 | expect(Store.state.myValue2).toBe(undefined) 125 | Store.resetAllToDefault() 126 | expect(Store.state.myValue1).toBe('foo') 127 | expect(Store.state.myValue2).toBe('bar') 128 | }) 129 | it('resetAllToInitial', () => { 130 | Store.setInitialState({ myValue1: 'foo', myValue2: 'bar' }) 131 | expect(Store.state.myValue1).toBe(undefined) 132 | expect(Store.state.myValue2).toBe(undefined) 133 | Store.resetAllToInitial() 134 | expect(Store.state.myValue1).toBe('foo') 135 | expect(Store.state.myValue2).toBe('bar') 136 | }) 137 | 138 | // it('setUpdatingHandlers: afterSet, afterDelete and afterUpdate are called', () => { 139 | // const afterSet = jest.fn() 140 | // const afterDelete = jest.fn() 141 | // const afterUpdate = jest.fn() 142 | // Store.setDefaultState({ myValue: 'bar' }) 143 | // Store.setUpdatingHandlers({ afterSet, afterDelete, afterUpdate }) 144 | // Store.value('myValue').set('foo') 145 | // expect(afterSet).toHaveBeenCalledWith('myValue', 'foo') 146 | // expect(afterUpdate).toHaveBeenCalledWith({ myValue: 'foo' }) 147 | // Store.value('myValue').resetToDefault() 148 | // expect(afterSet).toHaveBeenCalledWith('myValue', 'bar') 149 | // expect(afterUpdate).toHaveBeenCalledWith({ myValue: 'bar' }) 150 | // Store.value('myValue').remove() 151 | // expect(afterDelete).toHaveBeenCalledWith('myValue') 152 | // expect(afterUpdate).toHaveBeenCalledWith({}) 153 | // }) 154 | 155 | it('boolean.toogle', () => { 156 | Store.value('myValue').set(false) 157 | Store.boolean('myValue').toggle() 158 | expect(Store.state.myValue).toStrictEqual(true) 159 | Store.boolean('myValue').toggle() 160 | expect(Store.state.myValue).toStrictEqual(false) 161 | }) 162 | it('number.increment', () => { 163 | Store.value('myValue').set(0) 164 | Store.number('myValue').increment() 165 | expect(Store.state.myValue).toStrictEqual(1) 166 | Store.number('myValue').increment() 167 | expect(Store.state.myValue).toStrictEqual(2) 168 | }) 169 | it('string.append', () => { 170 | Store.value('myValue').set('foo') 171 | Store.string('myValue').append('bar') 172 | expect(Store.state.myValue).toStrictEqual('foobar') 173 | }) 174 | it('array.append', () => { 175 | Store.value('myValue').set(['foo']) 176 | Store.array('myValue').append('bar') 177 | expect(Store.state.myValue).toStrictEqual(['foo', 'bar']) 178 | }) 179 | it('object.setValue', () => { 180 | Store.value('myValue').set({ foo: 1 }) 181 | Store.object('myValue').setValue('bar', 2) 182 | expect(Store.state.myValue).toStrictEqual({ foo: 1, bar: 2 }) 183 | }) 184 | it('set.add', () => { 185 | Store.value('myValue').set(new Set(['foo'])) 186 | Store.set('myValue').add('bar') 187 | expect(Store.state.myValue).toStrictEqual(new Set(['foo', 'bar'])) 188 | }) 189 | it('map.setValue', () => { 190 | Store.value('myValue').set(new Map([['foo', 1]])) 191 | Store.map('myValue').setValue('bar', 2) 192 | expect(Store.state.myValue).toStrictEqual(new Map([['foo', 1], ['bar', 2]])) 193 | }) 194 | }) 195 | -------------------------------------------------------------------------------- /src/valueFunctions/valueFunctions.spec.js: -------------------------------------------------------------------------------- 1 | import ValueFunctions from './valueFunctions' 2 | 3 | describe('mutatorFunctions', () => { 4 | it('boolean.toggle', () => { 5 | const boolFalse = false 6 | const shouldBeTrue = ValueFunctions.boolean.mutator.toggle(boolFalse)() 7 | expect(shouldBeTrue).toBe(true) 8 | expect(shouldBeTrue).not.toBe(boolFalse) 9 | expect(ValueFunctions.boolean.mutator.toggle(true)()).toBe(false) 10 | }) 11 | 12 | it('number.increment', () => { 13 | const num = 1 14 | const shouldBe2 = ValueFunctions.number.mutator.increment(num)() 15 | expect(shouldBe2).toBe(2) 16 | expect(shouldBe2).not.toBe(num) 17 | expect(ValueFunctions.number.mutator.increment(num)(10)).toBe(11) 18 | expect(ValueFunctions.number.mutator.increment(num)(-10)).toBe(-9) 19 | expect(ValueFunctions.number.mutator.increment(num)(10, 5)).toBe(5) 20 | expect(ValueFunctions.number.mutator.increment(num)(-10, 0)).toBe(-9) 21 | }) 22 | it('number.decrement', () => { 23 | const num = 1 24 | const shouldBe0 = ValueFunctions.number.mutator.decrement(num)() 25 | expect(shouldBe0).toBe(0) 26 | expect(shouldBe0).not.toBe(num) 27 | expect(ValueFunctions.number.mutator.decrement(num)(10)).toBe(-9) 28 | expect(ValueFunctions.number.mutator.decrement(num)(-10)).toBe(11) 29 | expect(ValueFunctions.number.mutator.decrement(num)(10, 0)).toBe(0) 30 | expect(ValueFunctions.number.mutator.decrement(num)(-10, 0)).toBe(11) 31 | }) 32 | 33 | it('string.append', () => { 34 | const str = 'foo' 35 | const shouldBeFoobar = ValueFunctions.string.mutator.append(str)('bar') 36 | expect(shouldBeFoobar).toBe('foobar') 37 | expect(shouldBeFoobar).not.toBe(str) 38 | expect(ValueFunctions.string.mutator.append(str)('.1p😃')).toBe('foo.1p😃') 39 | expect(ValueFunctions.string.mutator.append(str)('')).toBe('foo') 40 | }) 41 | it('string.prepend', () => { 42 | const str = 'foo' 43 | const shouldBeBarfoo = ValueFunctions.string.mutator.prepend(str)('bar') 44 | expect(shouldBeBarfoo).toBe('barfoo') 45 | expect(shouldBeBarfoo).not.toBe(str) 46 | expect(ValueFunctions.string.mutator.prepend(str)('.1p😃')).toBe('.1p😃foo') 47 | expect(ValueFunctions.string.mutator.prepend(str)('')).toBe('foo') 48 | }) 49 | it('string.insert', () => { 50 | const str = 'foo' 51 | const shouldBeFobaro = ValueFunctions.string.mutator.insert(str)(2, 'bar') 52 | expect(shouldBeFobaro).toBe('fobaro') 53 | expect(shouldBeFobaro).not.toBe(str) 54 | expect(ValueFunctions.string.mutator.insert(str)(1, 'bar')).toBe('fbaroo') 55 | expect(ValueFunctions.string.mutator.insert(str)(1, '')).toBe('foo') 56 | }) 57 | it('string.replace', () => { 58 | const str = 'foobarfoo' 59 | const shouldBeBarbarfoo = ValueFunctions.string.mutator.replace(str)('foo', 'bar') 60 | expect(shouldBeBarbarfoo).toBe('barbarfoo') 61 | expect(shouldBeBarbarfoo).not.toBe(str) 62 | expect(ValueFunctions.string.mutator.replace(str)('bar', 'foo')).toBe('foofoofoo') 63 | }) 64 | it('string.substring', () => { 65 | const str = 'foobar' 66 | const shouldBeBar = ValueFunctions.string.mutator.substring(str)(3) 67 | expect(shouldBeBar).toBe('bar') 68 | expect(shouldBeBar).not.toBe(str) 69 | expect(ValueFunctions.string.mutator.substring(str)(0, 3)).toBe('foo') 70 | }) 71 | 72 | it('array.append', () => { 73 | const arr = [1, 2] 74 | const shouldBe123 = ValueFunctions.array.mutator.append(arr)(3) 75 | expect(shouldBe123).toStrictEqual([1, 2, 3]) 76 | expect(shouldBe123).not.toBe(arr) 77 | expect(ValueFunctions.array.mutator.append(arr)(3, 4)).toStrictEqual([1, 2, 3, 4]) 78 | }) 79 | it('array.prepend', () => { 80 | const arr = [1, 2] 81 | const shouldBe312 = ValueFunctions.array.mutator.prepend(arr)(3) 82 | expect(shouldBe312).toStrictEqual([3, 1, 2]) 83 | expect(shouldBe312).not.toBe(arr) 84 | expect(ValueFunctions.array.mutator.prepend(arr)(3, 4)).toStrictEqual([3, 4, 1, 2]) 85 | }) 86 | it('array.insert', () => { 87 | const arr = [1, 2] 88 | const shouldBe132 = ValueFunctions.array.mutator.insert(arr)(1, 3) 89 | expect(shouldBe132).toStrictEqual([1, 3, 2]) 90 | expect(shouldBe132).not.toBe(arr) 91 | expect(ValueFunctions.array.mutator.insert(arr)(0, 3)).toStrictEqual([3, 1, 2]) 92 | }) 93 | it('array.removeFirst', () => { 94 | const arr = [1, 2] 95 | const shouldBe1 = ValueFunctions.array.mutator.removeFirst(arr)() 96 | expect(shouldBe1).toStrictEqual([2]) 97 | expect(shouldBe1).not.toBe(arr) 98 | expect(ValueFunctions.array.mutator.removeFirst([1])()).toStrictEqual([]) 99 | expect(ValueFunctions.array.mutator.removeFirst([])()).toStrictEqual([]) 100 | }) 101 | it('array.removeLast', () => { 102 | const arr = [1, 2] 103 | const shouldBe2 = ValueFunctions.array.mutator.removeLast(arr)() 104 | expect(shouldBe2).toStrictEqual([1]) 105 | expect(shouldBe2).not.toBe(arr) 106 | expect(ValueFunctions.array.mutator.removeLast([1])()).toStrictEqual([]) 107 | expect(ValueFunctions.array.mutator.removeLast([])()).toStrictEqual([]) 108 | }) 109 | it('array.removeIndex', () => { 110 | const arr = [1, 2, 3] 111 | const shouldBe13 = ValueFunctions.array.mutator.removeIndex(arr)(1) 112 | expect(shouldBe13).toStrictEqual([1, 3]) 113 | expect(shouldBe13).not.toBe(arr) 114 | expect(ValueFunctions.array.mutator.removeIndex(arr)(0)).toStrictEqual([2, 3]) 115 | expect(ValueFunctions.array.mutator.removeIndex([1])(0)).toStrictEqual([]) 116 | expect(ValueFunctions.array.mutator.removeIndex([])(0)).toStrictEqual([]) 117 | }) 118 | it('array.remove', () => { 119 | const arr = [1, 2, 3] 120 | const shouldBe13 = ValueFunctions.array.mutator.remove(arr)(2) 121 | expect(shouldBe13).toStrictEqual([1, 3]) 122 | expect(shouldBe13).not.toBe(arr) 123 | expect(ValueFunctions.array.mutator.remove(arr)(0)).toStrictEqual([1, 2, 3]) 124 | expect(ValueFunctions.array.mutator.remove([1])(1)).toStrictEqual([]) 125 | expect(ValueFunctions.array.mutator.remove([])(1)).toStrictEqual([]) 126 | expect(ValueFunctions.array.mutator.remove([1, 2, 3, 2, 2])(2)).toStrictEqual([1, 3]) 127 | expect(ValueFunctions.array.mutator.remove([1, 2, 3])(1, 2)).toStrictEqual([3]) 128 | }) 129 | it('array.splice', () => { 130 | const arr = [1, 2, 3] 131 | const shouldBe143 = ValueFunctions.array.mutator.splice(arr)(1, 1, 4) 132 | expect(shouldBe143).toStrictEqual([1, 4, 3]) 133 | expect(shouldBe143).not.toBe(arr) 134 | expect(ValueFunctions.array.mutator.splice(arr)(0, 2, 5)).toStrictEqual([5, 3]) 135 | }) 136 | it('array.reverse', () => { 137 | const arr = [1, 2, 3] 138 | const shouldBe321 = ValueFunctions.array.mutator.reverse(arr)() 139 | expect(shouldBe321).toStrictEqual([3, 2, 1]) 140 | expect(shouldBe321).not.toBe(arr) 141 | }) 142 | it('array.sort', () => { 143 | const arr = [3, 1, 2] 144 | const shouldBe123 = ValueFunctions.array.mutator.sort(arr)() 145 | expect(shouldBe123).toStrictEqual([1, 2, 3]) 146 | expect(shouldBe123).not.toBe(arr) 147 | expect(ValueFunctions.array.mutator.sort(arr)((a, b) => b - a)).toStrictEqual([3, 2, 1]) 148 | }) 149 | 150 | it('object.setValue', () => { 151 | const obj = { foo: 1 } 152 | const shouldHaveBar = ValueFunctions.object.mutator.setValue(obj)('bar', 2) 153 | expect(shouldHaveBar).toStrictEqual({ foo: 1, bar: 2 }) 154 | expect(shouldHaveBar).not.toBe(obj) 155 | }) 156 | it('object.setValues', () => { 157 | const obj = { foo: 1 } 158 | const shouldHaveBar = ValueFunctions.object.mutator.setValues(obj)({ bar: 2 }) 159 | expect(shouldHaveBar).toStrictEqual({ foo: 1, bar: 2 }) 160 | expect(shouldHaveBar).not.toBe(obj) 161 | expect(ValueFunctions.object.mutator.setValues(obj)({ bar: 2, qux: 3 })).toStrictEqual({ foo: 1, bar: 2, qux: 3 }) 162 | }) 163 | it('object.remove', () => { 164 | const obj = { foo: 1, bar: 2 } 165 | const shouldHaveBar = ValueFunctions.object.mutator.remove(obj)('bar') 166 | expect(shouldHaveBar).toStrictEqual({ foo: 1 }) 167 | expect(shouldHaveBar).not.toBe(obj) 168 | expect(ValueFunctions.object.mutator.remove(obj)('bar', 'qux')).toStrictEqual({ foo: 1 }) 169 | expect(ValueFunctions.object.mutator.remove(obj)('foo', 'bar')).toStrictEqual({}) 170 | }) 171 | 172 | it('set.add', () => { 173 | const set = new Set([1, 2]) 174 | const shouldBe123 = ValueFunctions.set.mutator.add(set)(3) 175 | expect(shouldBe123).toStrictEqual(new Set([1, 2, 3])) 176 | expect(shouldBe123).not.toBe(set) 177 | expect(ValueFunctions.set.mutator.add(set)(3, 4)).toStrictEqual(new Set([1, 2, 3, 4])) 178 | }) 179 | it('set.remove', () => { 180 | const set = new Set([1, 2]) 181 | const shouldBe2 = ValueFunctions.set.mutator.remove(set)(1) 182 | expect(shouldBe2).toStrictEqual(new Set([2])) 183 | expect(shouldBe2).not.toBe(set) 184 | expect(ValueFunctions.set.mutator.remove(set)(2)).toStrictEqual(new Set([1])) 185 | expect(ValueFunctions.set.mutator.remove(set)(1, 2)).toStrictEqual(new Set([])) 186 | }) 187 | it('set.toggle', () => { 188 | const set = new Set([1, 2]) 189 | const shouldBe2 = ValueFunctions.set.mutator.toggle(set)(1) 190 | expect(shouldBe2).toStrictEqual(new Set([2])) 191 | expect(shouldBe2).not.toBe(set) 192 | expect(ValueFunctions.set.mutator.toggle([1])(2)).toStrictEqual(new Set([1, 2])) 193 | expect(ValueFunctions.set.mutator.toggle([1, 2])(2, 3)).toStrictEqual(new Set([1, 3])) 194 | }) 195 | 196 | it('map.setValue', () => { 197 | const map = new Map([['foo', 1]]) 198 | const shouldHaveBar = ValueFunctions.map.mutator.setValue(map)('bar', 2) 199 | expect(shouldHaveBar).toStrictEqual(new Map([['foo', 1], ['bar', 2]])) 200 | expect(shouldHaveBar).not.toBe(map) 201 | }) 202 | it('map.setValues', () => { 203 | const map = new Map([['foo', 1]]) 204 | const shouldHaveBar = ValueFunctions.map.mutator.setValues(map)([['bar', 2]]) 205 | expect(shouldHaveBar).toStrictEqual(new Map([['foo', 1], ['bar', 2]])) 206 | expect(shouldHaveBar).not.toBe(map) 207 | expect(ValueFunctions.map.mutator.setValues(map)([['bar', 2], ['qux', 3]])).toStrictEqual(new Map([['foo', 1], ['bar', 2], ['qux', 3]])) 208 | }) 209 | it('map.remove', () => { 210 | const map = new Map([['foo', 1], ['bar', 2]]) 211 | const shouldHaveBar = ValueFunctions.map.mutator.remove(map)('bar') 212 | expect(shouldHaveBar).toStrictEqual(new Map([['foo', 1]])) 213 | expect(shouldHaveBar).not.toBe(map) 214 | expect(ValueFunctions.map.mutator.remove(map)('bar', 'qux')).toStrictEqual(new Map([['foo', 1]])) 215 | expect(ValueFunctions.map.mutator.remove(map)('foo', 'bar')).toStrictEqual(new Map([])) 216 | }) 217 | }) 218 | -------------------------------------------------------------------------------- /src/components/values.spec.js: -------------------------------------------------------------------------------- 1 | import { h, nextTick } from 'vue' 2 | import { mount } from '@vue/test-utils' 3 | import Store from '../store' 4 | import { 5 | Value, 6 | BooleanValue, 7 | NumberValue, 8 | StringValue, 9 | ArrayValue, 10 | SetValue, 11 | ObjectValue, 12 | MapValue, 13 | StoredValue, 14 | StoredBooleanValue, 15 | StoredNumberValue, 16 | StoredStringValue, 17 | StoredArrayValue, 18 | StoredSetValue, 19 | StoredObjectValue, 20 | StoredMapValue, 21 | } from './index' 22 | 23 | const Tester = { 24 | name: 'Tester', 25 | inheritAttrs: false, 26 | render () { 27 | return h('div') 28 | }, 29 | } 30 | 31 | const getWrapper = (Component, props = {}) => mount(Component, { 32 | props, 33 | slots: { 34 | default: (scopedProps) => h(Tester, scopedProps), 35 | }, 36 | }) 37 | const getWrapperUtilities = (...args) => { 38 | const wrapper = getWrapper(...args) 39 | const tester = wrapper.findComponent(Tester) 40 | return { wrapper, tester, getAttrs: () => wrapper.findComponent(Tester).vm.$attrs } 41 | } 42 | 43 | beforeEach(() => { 44 | Store.removeAll() 45 | Store.setDefaultState() 46 | Store.setInitialState() 47 | }) 48 | 49 | function testGeneralValue ({ 50 | component: Component, 51 | storedComponent: StoredComponent, 52 | componentName, 53 | emptyValue = undefined, 54 | foo = 'foo', 55 | bar = 'bar', 56 | uid = 'myValue', 57 | customProps = {}, 58 | }) { 59 | if (StoredComponent) { 60 | if (Component) { 61 | testGeneralValue({ 62 | component: Component, 63 | componentName, 64 | emptyValue, 65 | foo, 66 | bar, 67 | customProps, 68 | }) 69 | } 70 | testGeneralValue({ 71 | component: StoredComponent, 72 | componentName: componentName.indexOf('Stored') === 0 ? componentName : `Stored${componentName}`, 73 | emptyValue, 74 | foo, 75 | bar, 76 | customProps: { uid, ...customProps }, 77 | }) 78 | return 79 | } 80 | const isStoredValue = componentName.indexOf('Stored') === 0 81 | const getUtilities = (props = {}) => getWrapperUtilities(Component, { ...customProps, ...props }) 82 | 83 | describe(`${componentName} (basics)`, () => { 84 | it('It renders correctly', async () => { 85 | Store.removeAll() 86 | expect(getWrapper(Component, customProps).findAllComponents(Tester)).toHaveLength(1) 87 | }) 88 | it('Get and Set work correctly', async () => { 89 | Store.removeAll() 90 | const { getAttrs } = getUtilities() 91 | expect(getAttrs().value).toStrictEqual(emptyValue) 92 | getAttrs().set(foo) 93 | await nextTick() 94 | expect(getAttrs().value).toStrictEqual(foo) 95 | }) 96 | it('change event is emitted', async () => { 97 | Store.removeAll() 98 | const { wrapper, getAttrs } = getUtilities({ initialValue: foo }) 99 | getAttrs().set(bar) 100 | await nextTick() 101 | expect(wrapper.emitted('change')[0]).toEqual([bar, foo]) 102 | }) 103 | it('change event not emitted for same value', async () => { 104 | Store.removeAll() 105 | const { wrapper, getAttrs } = getUtilities({ initialValue: foo }) 106 | getAttrs().set(foo) 107 | await nextTick() 108 | expect(wrapper.emitted('change')).toBe(undefined) 109 | }) 110 | it('Initializes with defaultValue', async () => { 111 | Store.removeAll() 112 | const { getAttrs } = getUtilities({ defaultValue: foo }) 113 | expect(getAttrs().value).toStrictEqual(foo) 114 | }) 115 | it('Initializes with initialValue', async () => { 116 | Store.removeAll() 117 | const { getAttrs } = getUtilities({ initialValue: foo }) 118 | expect(getAttrs().value).toStrictEqual(foo) 119 | }) 120 | it('Initializes with initialValue if defaultValue is present', async () => { 121 | Store.removeAll() 122 | const { getAttrs } = getUtilities({ initialValue: foo, defaultValue: bar }) 123 | expect(getAttrs().value).toStrictEqual(foo) 124 | }) 125 | it('Clear works correctly', async () => { 126 | Store.removeAll() 127 | const { getAttrs } = getUtilities({ initialValue: foo }) 128 | expect(getAttrs().value).toStrictEqual(foo) 129 | getAttrs().clear() 130 | await nextTick() 131 | expect(getAttrs().value).toStrictEqual(emptyValue) 132 | }) 133 | it('ResetToDefault works correctly', async () => { 134 | Store.removeAll() 135 | const { getAttrs } = getUtilities({ initialValue: foo, defaultValue: bar }) 136 | expect(getAttrs().value).toStrictEqual(foo) 137 | getAttrs().resetToDefault() 138 | await nextTick() 139 | expect(getAttrs().value).toStrictEqual(bar) 140 | }) 141 | it('ResetToInitial works correctly', async () => { 142 | Store.removeAll() 143 | const { getAttrs } = getUtilities({ initialValue: foo }) 144 | getAttrs().set(bar) 145 | await nextTick() 146 | expect(getAttrs().value).toStrictEqual(bar) 147 | getAttrs().resetToInitial() 148 | await nextTick() 149 | expect(getAttrs().value).toStrictEqual(foo) 150 | }) 151 | it('Reset set to default', async () => { 152 | Store.removeAll() 153 | const { getAttrs } = getUtilities({ initialValue: foo, defaultValue: bar }) 154 | expect(getAttrs().value).toStrictEqual(foo) 155 | getAttrs().reset() 156 | await nextTick() 157 | expect(getAttrs().value).toStrictEqual(bar) 158 | }) 159 | it('Reset set to initial if default is not present', async () => { 160 | Store.removeAll() 161 | const { getAttrs } = getUtilities({ initialValue: foo }) 162 | getAttrs().set(bar) 163 | await nextTick() 164 | expect(getAttrs().value).toStrictEqual(bar) 165 | getAttrs().reset() 166 | await nextTick() 167 | expect(getAttrs().value).toStrictEqual(foo) 168 | }) 169 | if (!isStoredValue) { 170 | it('Reset set to empty if default nor initial are not present', async () => { 171 | const { getAttrs } = getUtilities() 172 | getAttrs().set(bar) 173 | await nextTick() 174 | expect(getAttrs().value).toStrictEqual(bar) 175 | getAttrs().reset() 176 | await nextTick() 177 | expect(getAttrs().value).toStrictEqual(emptyValue) 178 | }) 179 | } 180 | if (isStoredValue) { 181 | it('Initializes with defaultValue from store', async () => { 182 | Store.removeAll() 183 | Store.value(uid).set(foo) 184 | const { getAttrs } = getUtilities() 185 | expect(getAttrs().value).toStrictEqual(foo) 186 | }) 187 | it('Value is persisted into store', async () => { 188 | Store.removeAll() 189 | expect(Store.state[uid]).toStrictEqual(undefined) 190 | const { getAttrs } = getUtilities({ defaultValue: foo }) 191 | expect(Store.state[uid]).toStrictEqual(foo) 192 | getAttrs().set(bar) 193 | expect(Store.state[uid]).toStrictEqual(bar) 194 | }) 195 | it('ResetToDefault from store', async () => { 196 | Store.removeAll() 197 | Store.setDefaultValue(uid, foo) 198 | const { getAttrs } = getUtilities() 199 | expect(getAttrs().value).toStrictEqual(emptyValue) 200 | Store.value(uid).resetToDefault() 201 | await nextTick() 202 | expect(getAttrs().value).toStrictEqual(foo) 203 | }) 204 | it('ResetToInitial from store', async () => { 205 | Store.removeAll() 206 | Store.setInitialValue(uid, foo) 207 | const { getAttrs } = getUtilities() 208 | expect(getAttrs().value).toStrictEqual(emptyValue) 209 | Store.value(uid).resetToInitial() 210 | await nextTick() 211 | expect(getAttrs().value).toStrictEqual(foo) 212 | }) 213 | it('Reset to default (prior to initial) from store', async () => { 214 | Store.removeAll() 215 | Store.setDefaultValue(uid, foo) 216 | Store.setInitialValue(uid, bar) 217 | const { getAttrs } = getUtilities() 218 | expect(getAttrs().value).toStrictEqual(emptyValue) 219 | Store.value(uid).reset() 220 | await nextTick() 221 | expect(getAttrs().value).toStrictEqual(foo) 222 | }) 223 | it('Reset to initial (if default is not present) from store', async () => { 224 | Store.removeAll() 225 | Store.setInitialValue(uid, bar) 226 | const { getAttrs } = getUtilities() 227 | expect(getAttrs().value).toStrictEqual(emptyValue) 228 | Store.value(uid).reset() 229 | await nextTick() 230 | expect(getAttrs().value).toStrictEqual(bar) 231 | }) 232 | it('Reacts to a stored value change', async () => { 233 | Store.removeAll() 234 | const { getAttrs } = getUtilities({ initialValue: foo }) 235 | expect(getAttrs().value).toStrictEqual(foo) 236 | Store.value(uid).set(bar) 237 | await nextTick() 238 | expect(getAttrs().value).toStrictEqual(bar) 239 | }) 240 | } 241 | it('Set is ignored when disabled', async () => { 242 | Store.removeAll() 243 | const { getAttrs } = getUtilities({ disabled: true }) 244 | expect(getAttrs().value).toStrictEqual(emptyValue) 245 | getAttrs().set(foo) 246 | await nextTick() 247 | expect(getAttrs().value).toStrictEqual(emptyValue) 248 | }) 249 | }) 250 | } 251 | 252 | testGeneralValue({ 253 | component: Value, 254 | storedComponent: StoredValue, 255 | componentName: 'Value', 256 | }) 257 | testGeneralValue({ 258 | component: BooleanValue, 259 | storedComponent: StoredBooleanValue, 260 | componentName: 'BooleanValue', 261 | emptyValue: false, 262 | foo: false, 263 | bar: true, 264 | }) 265 | testGeneralValue({ 266 | component: NumberValue, 267 | storedComponent: StoredNumberValue, 268 | componentName: 'NumberValue', 269 | foo: 1, 270 | bar: 2, 271 | }) 272 | testGeneralValue({ 273 | component: StringValue, 274 | storedComponent: StoredStringValue, 275 | componentName: 'StringValue', 276 | foo: 'false', 277 | bar: 'true', 278 | }) 279 | testGeneralValue({ 280 | component: ArrayValue, 281 | storedComponent: StoredArrayValue, 282 | componentName: 'ArrayValue', 283 | emptyValue: [], 284 | foo: [1, 2], 285 | bar: [3, 4], 286 | }) 287 | testGeneralValue({ 288 | component: ObjectValue, 289 | storedComponent: StoredObjectValue, 290 | componentName: 'ObjectValue', 291 | emptyValue: {}, 292 | foo: { one: 1, two: 2 }, 293 | bar: { three: 3, four: 4 }, 294 | }) 295 | testGeneralValue({ 296 | component: SetValue, 297 | storedComponent: StoredSetValue, 298 | componentName: 'SetValue', 299 | emptyValue: new Set(), 300 | foo: new Set([1, 2]), 301 | bar: new Set([3, 4]), 302 | }) 303 | testGeneralValue({ 304 | component: MapValue, 305 | storedComponent: StoredMapValue, 306 | componentName: 'MapValue', 307 | emptyValue: new Map(), 308 | foo: new Map([[1, 2]]), 309 | bar: new Map([[3, 4]]), 310 | }) 311 | 312 | describe('StoredValue with uid prepared for nested objects', () => { 313 | const getUtilities = (props = {}) => getWrapperUtilities(StoredValue, props) 314 | const emptyValue = undefined 315 | const foo = 'foo' 316 | const uid1 = 'myObject' 317 | const uid2 = 'myValue' 318 | const uid = `${uid1}.${uid2}` 319 | 320 | it('Get and Set work correctly', async () => { 321 | Store.removeAll() 322 | const { getAttrs } = getUtilities({ uid }) 323 | expect(getAttrs().value).toStrictEqual(emptyValue) 324 | getAttrs().set(foo) 325 | await nextTick() 326 | expect(Store.state[uid1][uid2]).toStrictEqual(foo) 327 | expect(getAttrs().value).toStrictEqual(foo) 328 | }) 329 | }) 330 | 331 | describe('NumberValue (specific)', () => { 332 | const getUtilities = (props = {}) => getWrapperUtilities(NumberValue, props) 333 | 334 | it('isFirst works correctly', async () => { 335 | const { getAttrs } = getUtilities({ min: 1, initialValue: 5 }) 336 | expect(getAttrs().isFirst).toStrictEqual(false) 337 | getAttrs().set(1) 338 | await nextTick() 339 | expect(getAttrs().isFirst).toStrictEqual(true) 340 | }) 341 | it('isLast works correctly', async () => { 342 | const { getAttrs } = getUtilities({ max: 10, initialValue: 5 }) 343 | expect(getAttrs().isLast).toStrictEqual(false) 344 | getAttrs().set(10) 345 | await nextTick() 346 | expect(getAttrs().isLast).toStrictEqual(true) 347 | }) 348 | it('Avoid set number less than mininum', async () => { 349 | const { getAttrs } = getUtilities({ min: 1, initialValue: 5 }) 350 | expect(getAttrs().value).toStrictEqual(5) 351 | getAttrs().set(-1) 352 | await nextTick() 353 | expect(getAttrs().value).toStrictEqual(1) 354 | }) 355 | it('Avoid set number greater than maximum', async () => { 356 | const { getAttrs } = getUtilities({ max: 10, initialValue: 5 }) 357 | getAttrs().set(100) 358 | await nextTick() 359 | expect(getAttrs().value).toStrictEqual(10) 360 | }) 361 | }) 362 | 363 | describe('ArrayValue (specific)', () => { 364 | const getUtilities = (props = {}) => getWrapperUtilities(ArrayValue, props) 365 | 366 | it('First works correctly', async () => { 367 | const { getAttrs } = getUtilities({ initialValue: [1, 2, 3] }) 368 | expect(getAttrs().first()).toStrictEqual(1) 369 | }) 370 | it('Last works correctly', async () => { 371 | const { getAttrs } = getUtilities({ initialValue: [1, 2, 3] }) 372 | expect(getAttrs().last()).toStrictEqual(3) 373 | }) 374 | }) 375 | 376 | describe('Value helpers', () => { 377 | it('BooleanValue.toggle', async () => { 378 | const { getAttrs } = getWrapperUtilities(BooleanValue, { initialValue: false }) 379 | expect(getAttrs().value).toStrictEqual(false) 380 | getAttrs().toggle() 381 | await nextTick() 382 | expect(getAttrs().value).toStrictEqual(true) 383 | getAttrs().toggle() 384 | await nextTick() 385 | expect(getAttrs().value).toStrictEqual(false) 386 | }) 387 | it('NumberValue.increment', async () => { 388 | const { getAttrs } = getWrapperUtilities(NumberValue, { initialValue: 0 }) 389 | expect(getAttrs().value).toStrictEqual(0) 390 | getAttrs().increment() 391 | await nextTick() 392 | expect(getAttrs().value).toStrictEqual(1) 393 | getAttrs().increment() 394 | await nextTick() 395 | expect(getAttrs().value).toStrictEqual(2) 396 | }) 397 | it('StringValue.append', async () => { 398 | const { getAttrs } = getWrapperUtilities(StringValue, { initialValue: 'foo' }) 399 | expect(getAttrs().value).toStrictEqual('foo') 400 | getAttrs().append('bar') 401 | await nextTick() 402 | expect(getAttrs().value).toStrictEqual('foobar') 403 | }) 404 | it('ArrayValue.append', async () => { 405 | const { getAttrs } = getWrapperUtilities(ArrayValue, { initialValue: ['foo'] }) 406 | expect(getAttrs().value).toStrictEqual(['foo']) 407 | getAttrs().append('bar') 408 | await nextTick() 409 | expect(getAttrs().value).toStrictEqual(['foo', 'bar']) 410 | }) 411 | it('ObjectValue.setValue', async () => { 412 | const { getAttrs } = getWrapperUtilities(ObjectValue, { initialValue: { foo: 1 } }) 413 | expect(getAttrs().value).toStrictEqual({ foo: 1 }) 414 | getAttrs().setValue('bar', 2) 415 | await nextTick() 416 | expect(getAttrs().value).toStrictEqual({ foo: 1, bar: 2 }) 417 | }) 418 | it('SetValue.add', async () => { 419 | const { getAttrs } = getWrapperUtilities(SetValue, { initialValue: new Set(['foo']) }) 420 | expect(getAttrs().value).toStrictEqual(new Set(['foo'])) 421 | getAttrs().add('bar') 422 | await nextTick() 423 | expect(getAttrs().value).toStrictEqual(new Set(['foo', 'bar'])) 424 | }) 425 | it('MapValue.setValue', async () => { 426 | const { getAttrs } = getWrapperUtilities(MapValue, { initialValue: new Map([['foo', 1]]) }) 427 | expect(getAttrs().value).toStrictEqual(new Map([['foo', 1]])) 428 | getAttrs().setValue('bar', 2) 429 | await nextTick() 430 | expect(getAttrs().value).toStrictEqual(new Map([['foo', 1], ['bar', 2]])) 431 | }) 432 | }) 433 | --------------------------------------------------------------------------------