├── .babelrc ├── .circleci └── config.yml ├── .codeclimate.yml ├── .dockerignore ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── .hound.yml ├── .huskyrc.yml ├── .npmignore ├── .nvmrc ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── LICENSE ├── README.md ├── bower.json ├── build ├── karma.config.js ├── karma.dev.config.js ├── karma.sauce.config.js ├── local.runner.js ├── nightwatch.browserstack.config.js ├── nightwatch.config.js ├── prerelease.sh ├── rollup.config.js └── webpack.config.js ├── dist ├── vue-ls.js ├── vue-ls.min.js └── vue-ls.min.js.map ├── docker-compose.yml ├── examples └── counter │ └── index.html ├── gulpfile.js ├── package.json ├── src ├── index.js └── storage │ ├── MemoryStorage.js │ ├── WebStorage.js │ ├── WebStorageEvent.js │ └── index.js ├── test ├── e2e │ └── counter.js └── unit │ ├── ava │ ├── eventAttach.js │ ├── eventWindow.js │ ├── events.js │ ├── helpers │ │ ├── setupBrowserEnv.js │ │ ├── setupIEBrowserEnv.js │ │ └── setupOldBrowserEnv.js │ ├── localStorage.js │ ├── memoryFallback.js │ ├── memoryStorage.js │ ├── sessionStorage.js │ └── unknownStorage.js │ └── jasmine │ └── index.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": [ 7 | "> 1%", 8 | "Chrome >= 14", 9 | "Safari >= 4", 10 | "Firefox >= 4", 11 | "Opera >= 10", 12 | "Edge >= 41", 13 | "ie >= 9", 14 | "iOS >= 6", 15 | "ChromeAndroid >= 4", 16 | "OperaMobile >= 12" 17 | ] 18 | } 19 | }] 20 | ], 21 | "ignore": [ 22 | "dist/*.js", 23 | "packages/**/*.js" 24 | ], 25 | "plugins": [ 26 | "@babel/plugin-transform-runtime", 27 | "@babel/plugin-syntax-dynamic-import", 28 | "@babel/plugin-transform-modules-commonjs" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:10 6 | working_directory: ~/repo 7 | 8 | steps: 9 | - checkout 10 | - restore_cache: 11 | keys: 12 | - v1-dependencies-{{ checksum "package.json" }} 13 | - v1-dependencies- 14 | 15 | - run: NODE_ENV=dev yarn install 16 | 17 | - save_cache: 18 | paths: 19 | - node_modules 20 | key: v1-dependencies-{{ checksum "package.json" }} 21 | - run: yarn build 22 | - run: yarn test -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | eslint: 3 | enabled: true 4 | duplication: 5 | enabled: true 6 | config: 7 | languages: 8 | - javascript 9 | ratings: 10 | paths: 11 | - "src/**/*" 12 | exclude_paths: 13 | - "dist/**.js" 14 | - "examples/**/*" 15 | - "test/**/*" 16 | - "build/*" 17 | 18 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | 3 | # OS generated files # 4 | .DS_Store 5 | tests_output 6 | ehthumbs.db 7 | Icon? 8 | Thumbs.db 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | tab_width = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /test 2 | /dist 3 | /node_modules 4 | /build 5 | coverage 6 | gulpfile.js 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module', 6 | }, 7 | env: { 8 | browser: true, 9 | }, 10 | extends: 'airbnb-base', 11 | plugins: [], 12 | globals: { 13 | window: true, 14 | }, 15 | rules: { 16 | 'linebreak-style': 'off', 17 | 'semi-style': 0, 18 | 'no-cond-assign': 0, 19 | 'no-plusplus': 0, 20 | 'no-restricted-syntax': 0, 21 | 'global-require': 0, 22 | 'no-continue': 0, 23 | 'no-multi-assign': 0, 24 | 'no-empty': 0, 25 | 'guard-for-in': 0, 26 | 'camelcase': 0, 27 | 'quote-props': 0, 28 | 'consistent-return': 0, 29 | 'no-confusing-arrow': 0, 30 | 'no-extra-boolean-cast': 0, 31 | 'no-lonely-if': 0, 32 | 'no-underscore-dangle': 0, 33 | 'import/prefer-default-export': 0, 34 | 'import/extensions': ['error', 'always', { 35 | js: 'never', 36 | }], 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | yarn.lock -diff 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: vue-ls 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | .DS_Store 3 | .idea 4 | node_modules 5 | bower_components 6 | npm-debug.log 7 | yarn-error.log 8 | .nyc_output 9 | coverage 10 | reports 11 | selenium-debug.log 12 | selenium-server.log 13 | local.log 14 | sauce_connect.log 15 | tests_output 16 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | jshint: 2 | ignore_file: .jshintignore 3 | config_file: .jshintrc 4 | -------------------------------------------------------------------------------- /.huskyrc.yml: -------------------------------------------------------------------------------- 1 | hooks: 2 | pre-commit: 'npm run lint' 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .idea 3 | .git 4 | .bowerrc 5 | .gitignore 6 | .travis.yml 7 | .babelrc 8 | .editorconfig 9 | .npmignore 10 | _config.yml 11 | circle.yml 12 | yarn.lock 13 | package.json 14 | .codeclimate.yml 15 | node_modules 16 | bower_components 17 | CHANGELOG.md 18 | test 19 | tests 20 | .hound.yml 21 | .jshintignore 22 | .jshintrc 23 | .nvmrc 24 | Dockerfile 25 | docker-compose.yml 26 | .gitattributes 27 | .dockerignore 28 | build 29 | tests_output 30 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "10" 5 | - "12" 6 | 7 | install: 8 | - npm install 9 | 10 | script: 11 | - npm run build 12 | - npm test 13 | 14 | after_success: 15 | - npm run coveralls 16 | - npm run codecov 17 | 18 | notifications: 19 | email: false 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 3.2.2 2 | - Upgrade dev dependencies 3 | - Fix tests 4 | 5 | ## 3.0.0 6 | - Rename class and global variable `VueLocalStorage` to `VueStorage` 7 | 8 | ## 2.4.0 9 | - Added session storage 10 | - Added memory storage 11 | - Added the ability to name a variable 12 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at ognichenko.igor@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.9.4-alpine as builder 2 | 3 | WORKDIR /app 4 | 5 | RUN npm install -g node-static 6 | COPY ./examples ./examples 7 | COPY ./dist ./dist 8 | 9 | CMD ["static", ".", "-p", "3000", "-a", "0.0.0.0"] 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Igor Ognichenko 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | vue-ls logo 4 | 5 |

6 | 7 |

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

17 | 18 |

19 | 20 | 21 | 22 | 23 | 24 |

25 | 26 | 27 | # vue-ls 28 | 29 | [![Greenkeeper badge](https://badges.greenkeeper.io/RobinCK/vue-ls.svg)](https://greenkeeper.io/) 30 | 31 | Vue plugin for work with local storage, session storage and memory storage from Vue context 32 | 33 | [![NPM](https://nodei.co/npm/vue-ls.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/vue-ls/) 34 | 35 | ## jsFiddle Example 36 | 37 | [Vue 1.x](https://jsfiddle.net/Robin_ck/Lvb2ah5p/) 38 | 39 | [Vue 2.x](https://jsfiddle.net/Robin_ck/6x1akv1L/) 40 | 41 | ## Install 42 | #### CDN 43 | 44 | Recommended: https://unpkg.com/vue-ls, which will reflect the latest version as soon as it is published to npm. You can also browse the source of the npm package at https://unpkg.com/vue-ls/ 45 | 46 | Also available on jsDelivr or cdnjs, but these two services take some time to sync so the latest release may not be available yet. 47 | 48 | #### NPM 49 | 50 | ``` bash 51 | npm install vue-ls --save 52 | ``` 53 | 54 | #### Yarn 55 | 56 | ``` bash 57 | yarn add vue-ls 58 | ``` 59 | 60 | #### Bower 61 | 62 | ``` bash 63 | bower install vue-ls --save 64 | ``` 65 | 66 | ## Development Setup 67 | 68 | ``` bash 69 | # install dependencies 70 | npm install 71 | 72 | # build dist files 73 | npm run build 74 | ``` 75 | 76 | ## Usage 77 | 78 | Vue storage API. 79 | 80 | ``` js 81 | import Storage from 'vue-ls'; 82 | 83 | const options = { 84 | namespace: 'vuejs__', // key prefix 85 | name: 'ls', // name variable Vue.[ls] or this.[$ls], 86 | storage: 'local', // storage name session, local, memory 87 | }; 88 | 89 | Vue.use(Storage, options); 90 | 91 | //or 92 | //Vue.use(Storage); 93 | 94 | new Vue({ 95 | el: '#app', 96 | mounted: function() { 97 | Vue.ls.set('foo', 'boo'); 98 | //Set expire for item 99 | Vue.ls.set('foo', 'boo', 60 * 60 * 1000); //expiry 1 hour 100 | Vue.ls.get('foo'); 101 | Vue.ls.get('boo', 10); //if not set boo returned default 10 102 | 103 | let callback = (val, oldVal, uri) => { 104 | console.log('localStorage change', val); 105 | } 106 | 107 | Vue.ls.on('foo', callback) //watch change foo key and triggered callback 108 | Vue.ls.off('foo', callback) //unwatch 109 | 110 | Vue.ls.remove('foo'); 111 | } 112 | }); 113 | ``` 114 | Use in js file 115 | ``` js 116 | // localStore.js 117 | import Storage from 'vue-ls'; 118 | const options = { 119 | namespace: 'vuejs__', // key prefix 120 | name: 'ls', // name variable Vue.[ls] or this.[$ls], 121 | storage: 'local', // storage name session, local, memory 122 | }; 123 | 124 | const { ls } = Storage.useStorage(options) 125 | 126 | export default ls 127 | 128 | // somefile.js 129 | import ls from 'localStore.js'; 130 | 131 | ls.set('foo', 'boo'); 132 | ls.get('foo'); 133 | ``` 134 | 135 | #### Global 136 | 137 | - `Vue.ls` 138 | 139 | #### Context 140 | - `this.$ls` 141 | 142 | ## API 143 | 144 | #### `Vue.ls.get(name, def)` 145 | 146 | Returns value under `name` in storage. Internally parses the value from JSON before returning it. 147 | 148 | - `def`: default null, returned if not set `name`. 149 | 150 | #### `Vue.ls.set(name, value, expire)` 151 | 152 | Persists `value` under `name` in storage. Internally converts the `value` to JSON. 153 | 154 | - `expire`: default null, life time in milliseconds `name` 155 | 156 | #### `Vue.ls.remove(name)` 157 | 158 | Removes `name` from storage. Returns `true` if the property was successfully deleted, and `false` otherwise. 159 | 160 | #### `Vue.ls.clear()` 161 | 162 | Clears storage. 163 | 164 | #### `Vue.ls.on(name, callback)` 165 | 166 | Listen for changes persisted against `name` on other tabs. Triggers `callback` when a change occurs, passing the following arguments. 167 | 168 | - `newValue`: the current value for `name` in storage, parsed from the persisted JSON 169 | - `oldValue`: the old value for `name` in storage, parsed from the persisted JSON 170 | - `url`: the url for the tab where the modification came from 171 | 172 | #### `Vue.ls.off(name, callback)` 173 | 174 | Removes a listener previously attached with `Vue.ls.on(name, callback)`. 175 | 176 | ## Testing 177 | 178 | - `npm run test` - run unit test 179 | - `npm run test:browserstack` - run browser test 180 | - `npm run test:browserstack:chrome` 181 | - `npm run test:browserstack:ie` 182 | - `npm run test:browserstack:edge` 183 | - `npm run test:browserstack:firefox` 184 | - `npm run test:browserstack:safari` 185 | - `npm run test:chrome` - run browser test in chrome 186 | 187 | Testing Supported By
188 | 189 | 190 | ## Note 191 | Some browsers don't support the storage event, and most of the browsers that do support it will only call it when the storage is changed by a different window. So, open your page up in two windows. Click the links in one window and you will probably see the event in the other. 192 | 193 | The assumption is that your page will already know all interactions with localStorage in its own window and only needs notification when a different window changes things. This, of course, is a foolish assumption. But. 194 | 195 | ## Other my Vue JS plugins 196 | 197 | | Project | Status | Description | 198 | |---------|--------|-------------| 199 | | [vue-gallery](https://github.com/RobinCK/vue-gallery) | ![npm](https://img.shields.io/npm/v/vue-gallery.svg) | VueJS responsive and customizable image and video gallery | 200 | | [vue-popper](https://github.com/RobinCK/vue-popper) | ![npm](https://img.shields.io/npm/v/vue-popperjs.svg) | VueJS popover component based on
popper.js | 201 | 202 | ## Contributors 203 | 204 | ### Code Contributors 205 | 206 | This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. 207 | 208 | 209 | ### Financial Contributors 210 | 211 | Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/vue-ls/contribute)] 212 | 213 | #### Individuals 214 | 215 | 216 | 217 | #### Organizations 218 | 219 | Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/vue-ls/contribute)] 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | ## License 233 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2FRobinCK%2Fvue-ls.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2FRobinCK%2Fvue-ls?ref=badge_large) 234 | 235 | MIT © [Igor Ognichenko](https://github.com/RobinCK) 236 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-ls", 3 | "version": "3.1.0", 4 | "description": "Vue plugin for work with LocalStorage from Vue context", 5 | "main": "dist/vue-ls.js", 6 | "authors": [ 7 | "Igor Ognichenko " 8 | ], 9 | "license": "MIT", 10 | "keywords": [ 11 | "storage", 12 | "localstorage", 13 | "local-storage", 14 | "vue-localstorage", 15 | "vuejs-localstorage", 16 | "vue-local-storage", 17 | "vuejs-local-storage", 18 | "memorystroage", 19 | "sessionstorage", 20 | "session-storage", 21 | "vue-sessionstorage", 22 | "vuejs-sessionstorage", 23 | "vue-session-storage", 24 | "vuejs-session-storage", 25 | "memory-stroage", 26 | "vue-ls", 27 | "vue", 28 | "vuejs", 29 | "vue-plugin", 30 | "watch", 31 | "es6-modules" 32 | ], 33 | "homepage": "https://github.com/RobinCK/vue-ls", 34 | "ignore": [ 35 | ".idea", 36 | ".git", 37 | ".bowerrc", 38 | ".gitignore", 39 | ".travis.yml", 40 | ".babelrc", 41 | ".editorconfig", 42 | ".npmignore", 43 | "_config.yml", 44 | "circle.yml", 45 | "yarn.lock", 46 | "package.json", 47 | ".codeclimate.yml", 48 | "node_modules", 49 | "bower_components", 50 | "CHANGELOG.md", 51 | "test", 52 | "tests", 53 | ".hound.yml", 54 | ".jshintignore", 55 | ".jshintrc", 56 | ".nvmrc", 57 | "Dockerfile", 58 | "docker-compose.yml", 59 | ".gitattributes", 60 | ".dockerignore", 61 | "build" 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /build/karma.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | frameworks: ['jasmine'], 3 | basePath: '../', 4 | files: [ 5 | 'test/unit/jasmine/*.js' 6 | ], 7 | preprocessors: { 8 | 'src/**/*.js': ['webpack'], 9 | 'test/**/*.js': ['webpack'] 10 | }, 11 | webpack: require('./webpack.config.js'), 12 | webpackMiddleware: { 13 | noInfo: true 14 | }, 15 | plugins: [ 16 | 'karma-webpack', 17 | 'karma-chrome-launcher', 18 | 'karma-jasmine' 19 | ] 20 | }; 21 | -------------------------------------------------------------------------------- /build/karma.dev.config.js: -------------------------------------------------------------------------------- 1 | const base = require('./karma.config.js'); 2 | 3 | module.exports = function (config) { 4 | config.set(Object.assign(base, { 5 | singleRun: true, 6 | browsers: ['Chrome'], 7 | reporters: ['progress'], 8 | plugins: base.plugins.concat([ 9 | 'karma-chrome-launcher' 10 | ]) 11 | })) 12 | }; 13 | -------------------------------------------------------------------------------- /build/karma.sauce.config.js: -------------------------------------------------------------------------------- 1 | const base = require('./karma.config.js'); 2 | const batches = { 3 | sl_edge_13: { 4 | base: 'SauceLabs', 5 | browserName: 'MicrosoftEdge', 6 | platform: 'Windows 10', 7 | version: '13' 8 | }, 9 | sl_chrome: { 10 | base: 'SauceLabs', 11 | browserName: 'chrome', 12 | platform: 'Windows 10' 13 | }, 14 | sl_firefox: { 15 | base: 'SauceLabs', 16 | browserName: 'firefox' 17 | }, 18 | sl_mac_safari: { 19 | base: 'SauceLabs', 20 | browserName: 'safari', 21 | platform: 'OS X 10.15' 22 | } 23 | }; 24 | 25 | module.exports = function(config) { 26 | config.set(Object.assign(base, { 27 | singleRun: true, 28 | browsers: Object.keys(batches), 29 | customLaunchers: batches, 30 | reporters: process.env.CI ? ['dots', 'saucelabs'] : ['progress', 'saucelabs'], 31 | sauceLabs: { 32 | testName: 'vue-ls', 33 | username: process.env.SAUCE_USERNAME, 34 | accessKey: process.env.SAUCE_ACCESS_KEY, 35 | recordScreenshots: false, 36 | sauceLabs: { 37 | testName: 'Vue.js unit tests', 38 | recordScreenshots: false, 39 | connectOptions: { 40 | 'no-ssl-bump-domains': 'all' // Ignore SSL error on Android emulator 41 | }, 42 | build: process.env.CIRCLE_BUILD_NUM || process.env.SAUCE_BUILD_ID || Date.now() 43 | }, 44 | public: 'public', 45 | build: process.env.BUILD_NUMBER || process.env.BUILD_TAG || process.env.CI_BUILD_NUMBER || 46 | process.env.CI_BUILD_TAG || process.env.TRAVIS_BUILD_NUMBER || process.env.CIRCLE_BUILD_NUM || 47 | process.env.DRONE_BUILD_NUMBER|| Date.now() 48 | }, 49 | plugins: base.plugins.concat([ 50 | 'karma-sauce-launcher' 51 | ]) 52 | })) 53 | }; 54 | -------------------------------------------------------------------------------- /build/local.runner.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const Nightwatch = require('nightwatch'); 4 | const browserstack = require('browserstack-local'); 5 | const serveStatic = require('serve-static'); 6 | const connect = require('connect'); 7 | const http = require('http'); 8 | const path = require('path'); 9 | let bs_local; 10 | let httpServer; 11 | 12 | // eslint-disable-next-line 13 | const logger = console.log; 14 | 15 | try { 16 | logger("Start server"); 17 | 18 | const app = connect().use(serveStatic(path.resolve('./'))); 19 | httpServer = http.createServer(app).listen(9000, () => { 20 | process.mainModule.filename = "./node_modules/nightwatch/bin/nightwatch" 21 | // Code to start browserstack local before start of test 22 | 23 | logger("Connecting local"); 24 | Nightwatch.bs_local = bs_local = new browserstack.Local(); 25 | 26 | bs_local.start({'key': process.env.BROWSERSTACK_ACCESS_KEY }, (error) => { 27 | if (error) { 28 | throw error; 29 | } 30 | 31 | logger('Connected. Now testing...'); 32 | 33 | Nightwatch.cli((argv) => { 34 | Nightwatch.CliRunner(argv) 35 | .setup(null, () => { 36 | // Code to stop browserstack local after end of parallel test 37 | bs_local.stop(() => {}); 38 | }) 39 | .runTests(() => { 40 | // Code to stop browserstack local after end of single test 41 | bs_local.stop(() => { 42 | logger("Stop Server"); 43 | httpServer.close(); 44 | }); 45 | }); 46 | }); 47 | }); 48 | }); 49 | } catch (ex) { 50 | logger('There was an error while starting the test runner:\n\n'); 51 | process.stderr.write(ex.stack + '\n'); 52 | process.exit(2); 53 | } 54 | -------------------------------------------------------------------------------- /build/nightwatch.browserstack.config.js: -------------------------------------------------------------------------------- 1 | const nightwatch_config = { 2 | src_folders : [ "test/e2e" ], 3 | 4 | selenium : { 5 | "start_process" : false, 6 | "host" : "hub-cloud.browserstack.com", 7 | "port" : 80 8 | }, 9 | 10 | test_settings: { 11 | default: { 12 | desiredCapabilities: { 13 | 'build': 'nightwatch-browserstack', 14 | 'browserstack.user': process.env.BROWSERSTACK_USERNAME || 'BROWSERSTACK_USERNAME', 15 | 'browserstack.key': process.env.BROWSERSTACK_ACCESS_KEY || 'BROWSERSTACK_ACCESS_KEY', 16 | 'browserstack.debug': true, 17 | 'browserstack.local': true, 18 | 'browser': 'chrome' 19 | } 20 | }, 21 | bstack_edge: { 22 | 'desiredCapabilities': { 23 | 'browserName': 'MicrosoftEdge', 24 | 'os': 'Windows', 25 | 'os_version': '10', 26 | 'browser': 'Edge', 27 | 'resolution': '1024x768' 28 | } 29 | }, 30 | bstack_chrome: { 31 | desiredCapabilities: { 32 | 'browserName': 'Chrome', 33 | 'os': 'Windows', 34 | 'os_version': '10', 35 | 'browser': 'Chrome', 36 | 'resolution': '1024x768' 37 | } 38 | }, 39 | bstack_firefox: { 40 | desiredCapabilities: { 41 | 'browserName': 'Firefox', 42 | 'os': 'Windows', 43 | 'os_version': '7', 44 | 'browser': 'Firefox', 45 | 'resolution': '1024x768' 46 | } 47 | }, 48 | bstack_firefox_osx: { 49 | desiredCapabilities: { 50 | 'browserName': 'Firefox', 51 | 'os': 'OS X', 52 | 'os_version': 'El Capitan', 53 | 'browser': 'Firefox', 54 | 'resolution': '1024x768' 55 | } 56 | }, 57 | bstack_safari: { 58 | desiredCapabilities: { 59 | 'browserName': 'Safari', 60 | 'os': 'OS X', 61 | 'os_version': 'Sierra', 62 | 'browser': 'Safari', 63 | 'resolution': '1024x768' 64 | } 65 | } 66 | } 67 | }; 68 | 69 | // Code to copy seleniumhost/port into test settings 70 | for (let i in nightwatch_config.test_settings) { 71 | const config = nightwatch_config.test_settings[i]; 72 | 73 | config['selenium_host'] = nightwatch_config.selenium.host; 74 | config['selenium_port'] = nightwatch_config.selenium.port; 75 | } 76 | 77 | module.exports = nightwatch_config; 78 | -------------------------------------------------------------------------------- /build/nightwatch.config.js: -------------------------------------------------------------------------------- 1 | const seleniumServer = require('selenium-server'); 2 | 3 | module.exports = { 4 | 'src_folders': [ 5 | 'test/e2e' 6 | ], 7 | 'output_folder': 'reports', 8 | 'test_workers': false, 9 | 10 | 'selenium' : { 11 | 'start_process' : true, 12 | 'server_path' : seleniumServer.path, 13 | 'port' : 4444, 14 | 'cli_args' : { 15 | 'webdriver.chrome.driver' : './node_modules/.bin/chromedriver', 16 | } 17 | }, 18 | 19 | 'test_settings': { 20 | 'default': { 21 | 'selenium_port': 4444, 22 | 'selenium_host': '127.0.0.1', 23 | 'silent': true, 24 | 'desiredCapabilities': { 25 | 'build': 'nightwatch', 26 | 'project': 'vue-ls' 27 | } 28 | }, 29 | 'chrome': { 30 | 'desiredCapabilities': { 31 | 'browserName': 'chrome', 32 | 'resolution': '1024x768' 33 | } 34 | }, 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /build/prerelease.sh: -------------------------------------------------------------------------------- 1 | npm run lint 2 | npm run test 3 | npm run test:unit 4 | npm run test:e2e 5 | npm run build 6 | -------------------------------------------------------------------------------- /build/rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import {uglify} from 'rollup-plugin-uglify'; 3 | import localResolve from 'rollup-plugin-local-resolve'; 4 | 5 | export default { 6 | input: 'src/index.js', 7 | plugins: [ 8 | localResolve(), 9 | babel({ 10 | babelrc: false, 11 | presets: [ 12 | ["@babel/preset-env", { 13 | modules: false, 14 | targets: { 15 | browsers: [ 16 | "> 1%", 17 | "Chrome >= 14", 18 | "Safari >= 4", 19 | "Firefox >= 4", 20 | "Opera >= 10", 21 | "Edge >= 41", 22 | "ie >= 9", 23 | "iOS >= 6", 24 | "ChromeAndroid >= 4", 25 | "OperaMobile >= 12" 26 | ] 27 | } 28 | }] 29 | ], 30 | runtimeHelpers: true, 31 | externalHelpers: false, 32 | exclude: 'node_modules/**', 33 | }), 34 | (process.env.NODE_ENV === 'production' && uglify()) 35 | ], 36 | output: { 37 | file: process.env.NODE_ENV === 'production' ? 'dist/vue-ls.min.js' : 'dist/vue-ls.js', 38 | format: 'umd', 39 | name: 'VueStorage', 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /build/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | 3 | module.exports = { 4 | mode: 'production', 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.js$/, 9 | loader: 'babel-loader', 10 | exclude: /node_modules/ 11 | } 12 | ] 13 | }, 14 | plugins: [ 15 | new webpack.DefinePlugin({ 16 | 'process.env': { 17 | NODE_ENV: '"development"', 18 | TRANSITION_DURATION: 50, 19 | TRANSITION_BUFFER: 10 20 | } 21 | }) 22 | ], 23 | devtool: 'inline-source-map' 24 | }; 25 | -------------------------------------------------------------------------------- /dist/vue-ls.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.VueStorage = factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | function _classCallCheck(instance, Constructor) { 8 | if (!(instance instanceof Constructor)) { 9 | throw new TypeError("Cannot call a class as a function"); 10 | } 11 | } 12 | 13 | function _defineProperties(target, props) { 14 | for (var i = 0; i < props.length; i++) { 15 | var descriptor = props[i]; 16 | descriptor.enumerable = descriptor.enumerable || false; 17 | descriptor.configurable = true; 18 | if ("value" in descriptor) descriptor.writable = true; 19 | Object.defineProperty(target, descriptor.key, descriptor); 20 | } 21 | } 22 | 23 | function _createClass(Constructor, protoProps, staticProps) { 24 | if (protoProps) _defineProperties(Constructor.prototype, protoProps); 25 | if (staticProps) _defineProperties(Constructor, staticProps); 26 | return Constructor; 27 | } 28 | 29 | function _defineProperty(obj, key, value) { 30 | if (key in obj) { 31 | Object.defineProperty(obj, key, { 32 | value: value, 33 | enumerable: true, 34 | configurable: true, 35 | writable: true 36 | }); 37 | } else { 38 | obj[key] = value; 39 | } 40 | 41 | return obj; 42 | } 43 | 44 | function ownKeys(object, enumerableOnly) { 45 | var keys = Object.keys(object); 46 | 47 | if (Object.getOwnPropertySymbols) { 48 | var symbols = Object.getOwnPropertySymbols(object); 49 | if (enumerableOnly) symbols = symbols.filter(function (sym) { 50 | return Object.getOwnPropertyDescriptor(object, sym).enumerable; 51 | }); 52 | keys.push.apply(keys, symbols); 53 | } 54 | 55 | return keys; 56 | } 57 | 58 | function _objectSpread2(target) { 59 | for (var i = 1; i < arguments.length; i++) { 60 | var source = arguments[i] != null ? arguments[i] : {}; 61 | 62 | if (i % 2) { 63 | ownKeys(Object(source), true).forEach(function (key) { 64 | _defineProperty(target, key, source[key]); 65 | }); 66 | } else if (Object.getOwnPropertyDescriptors) { 67 | Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); 68 | } else { 69 | ownKeys(Object(source)).forEach(function (key) { 70 | Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); 71 | }); 72 | } 73 | } 74 | 75 | return target; 76 | } 77 | 78 | /* eslint class-methods-use-this: off */ 79 | var ls = {}; 80 | 81 | var MemoryStorageInterface = 82 | /*#__PURE__*/ 83 | function () { 84 | function MemoryStorageInterface() { 85 | _classCallCheck(this, MemoryStorageInterface); 86 | 87 | Object.defineProperty(this, 'length', { 88 | /** 89 | * Define length property 90 | * 91 | * @return {number} 92 | */ 93 | get: function get() { 94 | return Object.keys(ls).length; 95 | } 96 | }); 97 | } 98 | /** 99 | * Get item 100 | * 101 | * @param {string} name 102 | * @returns {*} 103 | */ 104 | 105 | 106 | _createClass(MemoryStorageInterface, [{ 107 | key: "getItem", 108 | value: function getItem(name) { 109 | return name in ls ? ls[name] : null; 110 | } 111 | /** 112 | * Set item 113 | * 114 | * @param {string} name 115 | * @param {*} value 116 | * @returns {boolean} 117 | */ 118 | 119 | }, { 120 | key: "setItem", 121 | value: function setItem(name, value) { 122 | ls[name] = value; 123 | return true; 124 | } 125 | /** 126 | * Remove item 127 | * 128 | * @param {string} name 129 | * @returns {boolean} 130 | */ 131 | 132 | }, { 133 | key: "removeItem", 134 | value: function removeItem(name) { 135 | var found = name in ls; 136 | 137 | if (found) { 138 | return delete ls[name]; 139 | } 140 | 141 | return false; 142 | } 143 | /** 144 | * Clear storage 145 | * 146 | * @returns {boolean} 147 | */ 148 | 149 | }, { 150 | key: "clear", 151 | value: function clear() { 152 | ls = {}; 153 | return true; 154 | } 155 | /** 156 | * Get item by key 157 | * 158 | * @param {number} index 159 | * @returns {*} 160 | */ 161 | 162 | }, { 163 | key: "key", 164 | value: function key(index) { 165 | var keys = Object.keys(ls); 166 | return typeof keys[index] !== 'undefined' ? keys[index] : null; 167 | } 168 | }]); 169 | 170 | return MemoryStorageInterface; 171 | }(); 172 | 173 | var MemoryStorage = new MemoryStorageInterface(); 174 | 175 | var listeners = {}; 176 | /** 177 | * Event class 178 | */ 179 | 180 | var WebStorageEvent = 181 | /*#__PURE__*/ 182 | function () { 183 | function WebStorageEvent() { 184 | _classCallCheck(this, WebStorageEvent); 185 | } 186 | 187 | _createClass(WebStorageEvent, null, [{ 188 | key: "on", 189 | 190 | /** 191 | * Add storage change event 192 | * 193 | * @param {string} name 194 | * @param {Function} callback 195 | */ 196 | value: function on(name, callback) { 197 | if (typeof listeners[name] === 'undefined') { 198 | listeners[name] = []; 199 | } 200 | 201 | listeners[name].push(callback); 202 | } 203 | /** 204 | * Remove storage change event 205 | * 206 | * @param {string} name 207 | * @param {Function} callback 208 | */ 209 | 210 | }, { 211 | key: "off", 212 | value: function off(name, callback) { 213 | if (listeners[name].length) { 214 | listeners[name].splice(listeners[name].indexOf(callback), 1); 215 | } else { 216 | listeners[name] = []; 217 | } 218 | } 219 | /** 220 | * Emit event 221 | * 222 | * @param {Object} event 223 | */ 224 | 225 | }, { 226 | key: "emit", 227 | value: function emit(event) { 228 | var e = event || window.event; 229 | 230 | var getValue = function getValue(data) { 231 | try { 232 | return JSON.parse(data).value; 233 | } catch (err) { 234 | return data; 235 | } 236 | }; 237 | 238 | var fire = function fire(listener) { 239 | var newValue = getValue(e.newValue); 240 | var oldValue = getValue(e.oldValue); 241 | listener(newValue, oldValue, e.url || e.uri); 242 | }; 243 | 244 | if (typeof e === 'undefined' || typeof e.key === 'undefined') { 245 | return; 246 | } 247 | 248 | var all = listeners[e.key]; 249 | 250 | if (typeof all !== 'undefined') { 251 | all.forEach(fire); 252 | } 253 | } 254 | }]); 255 | 256 | return WebStorageEvent; 257 | }(); 258 | 259 | /** 260 | * Storage Bridge 261 | */ 262 | 263 | var WebStorage = 264 | /*#__PURE__*/ 265 | function () { 266 | /** 267 | * @param {Object} storage 268 | */ 269 | function WebStorage(storage) { 270 | _classCallCheck(this, WebStorage); 271 | 272 | this.storage = storage; 273 | this.options = { 274 | namespace: '', 275 | events: ['storage'] 276 | }; 277 | Object.defineProperty(this, 'length', { 278 | /** 279 | * Define length property 280 | * 281 | * @return {number} 282 | */ 283 | get: function get() { 284 | return this.storage.length; 285 | } 286 | }); 287 | 288 | if (typeof window !== 'undefined') { 289 | for (var i in this.options.events) { 290 | if (window.addEventListener) { 291 | window.addEventListener(this.options.events[i], WebStorageEvent.emit, false); 292 | } else if (window.attachEvent) { 293 | window.attachEvent("on".concat(this.options.events[i]), WebStorageEvent.emit); 294 | } else { 295 | window["on".concat(this.options.events[i])] = WebStorageEvent.emit; 296 | } 297 | } 298 | } 299 | } 300 | /** 301 | * Set Options 302 | * 303 | * @param {Object} options 304 | */ 305 | 306 | 307 | _createClass(WebStorage, [{ 308 | key: "setOptions", 309 | value: function setOptions() { 310 | var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 311 | this.options = Object.assign(this.options, options); 312 | } 313 | /** 314 | * Set item 315 | * 316 | * @param {string} name 317 | * @param {*} value 318 | * @param {number} expire - seconds 319 | */ 320 | 321 | }, { 322 | key: "set", 323 | value: function set(name, value) { 324 | var expire = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; 325 | var stringifyValue = JSON.stringify({ 326 | value: value, 327 | expire: expire !== null ? new Date().getTime() + expire : null 328 | }); 329 | this.storage.setItem(this.options.namespace + name, stringifyValue); 330 | } 331 | /** 332 | * Get item 333 | * 334 | * @param {string} name 335 | * @param {*} def - default value 336 | * @returns {*} 337 | */ 338 | 339 | }, { 340 | key: "get", 341 | value: function get(name) { 342 | var def = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 343 | var item = this.storage.getItem(this.options.namespace + name); 344 | 345 | if (item !== null) { 346 | try { 347 | var data = JSON.parse(item); 348 | 349 | if (data.expire === null) { 350 | return data.value; 351 | } 352 | 353 | if (data.expire >= new Date().getTime()) { 354 | return data.value; 355 | } 356 | 357 | this.remove(name); 358 | } catch (err) { 359 | return def; 360 | } 361 | } 362 | 363 | return def; 364 | } 365 | /** 366 | * Get item by key 367 | * 368 | * @param {number} index 369 | * @return {*} 370 | */ 371 | 372 | }, { 373 | key: "key", 374 | value: function key(index) { 375 | return this.storage.key(index); 376 | } 377 | /** 378 | * Remove item 379 | * 380 | * @param {string} name 381 | * @return {boolean} 382 | */ 383 | 384 | }, { 385 | key: "remove", 386 | value: function remove(name) { 387 | return this.storage.removeItem(this.options.namespace + name); 388 | } 389 | /** 390 | * Clear storage 391 | */ 392 | 393 | }, { 394 | key: "clear", 395 | value: function clear() { 396 | if (this.length === 0) { 397 | return; 398 | } 399 | 400 | var removedKeys = []; 401 | 402 | for (var i = 0; i < this.length; i++) { 403 | var key = this.storage.key(i); 404 | var regexp = new RegExp("^".concat(this.options.namespace, ".+"), 'i'); 405 | 406 | if (regexp.test(key) === false) { 407 | continue; 408 | } 409 | 410 | removedKeys.push(key); 411 | } 412 | 413 | for (var _key in removedKeys) { 414 | this.storage.removeItem(removedKeys[_key]); 415 | } 416 | } 417 | /** 418 | * Add storage change event 419 | * 420 | * @param {string} name 421 | * @param {Function} callback 422 | */ 423 | 424 | }, { 425 | key: "on", 426 | value: function on(name, callback) { 427 | WebStorageEvent.on(this.options.namespace + name, callback); 428 | } 429 | /** 430 | * Remove storage change event 431 | * 432 | * @param {string} name 433 | * @param {Function} callback 434 | */ 435 | 436 | }, { 437 | key: "off", 438 | value: function off(name, callback) { 439 | WebStorageEvent.off(this.options.namespace + name, callback); 440 | } 441 | }]); 442 | 443 | return WebStorage; 444 | }(); 445 | 446 | var _global = typeof window !== 'undefined' ? window : global || {}; 447 | /** 448 | * @type {{install: (function(Object, Object): WebStorage)}} 449 | */ 450 | 451 | 452 | var VueStorage = { 453 | /** 454 | * use storage 455 | * 456 | * @param {Object} options 457 | * @returns {WebStorage} 458 | */ 459 | useStorage: function useStorage(options) { 460 | var _options = _objectSpread2(_objectSpread2({}, options), {}, { 461 | storage: options.storage || 'local', 462 | name: options.name || 'ls' 463 | }); 464 | 465 | if (_options.storage && ['memory', 'local', 'session'].indexOf(_options.storage) === -1) { 466 | throw new Error("Vue-ls: Storage \"".concat(_options.storage, "\" is not supported")); 467 | } 468 | 469 | var store = null; 470 | 471 | switch (_options.storage) { 472 | // eslint-disable-line 473 | case 'local': 474 | try { 475 | store = 'localStorage' in _global ? _global.localStorage : null; 476 | } catch (e) {// In some situations the browser will throw a security exception when attempting to access 477 | } 478 | 479 | break; 480 | 481 | case 'session': 482 | try { 483 | store = 'sessionStorage' in _global ? _global.sessionStorage : null; 484 | } catch (e) {// In some situations the browser will throw a security exception when attempting to access 485 | } 486 | 487 | break; 488 | 489 | case 'memory': 490 | store = MemoryStorage; 491 | break; 492 | } 493 | 494 | if (!store) { 495 | store = MemoryStorage; // eslint-disable-next-line 496 | 497 | console.error("Vue-ls: Storage \"".concat(_options.storage, "\" is not supported your system, use memory storage")); 498 | } 499 | 500 | var ls = new WebStorage(store); 501 | ls.setOptions(Object.assign(ls.options, { 502 | namespace: '' 503 | }, _options || {})); 504 | return { 505 | ls: ls, 506 | _options: _options 507 | }; 508 | }, 509 | 510 | /** 511 | * Install plugin 512 | * 513 | * @param {Object} Vue 514 | * @param {Object} options 515 | * @returns {WebStorage} 516 | */ 517 | install: function install(Vue) { 518 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 519 | 520 | var _this$useStorage = this.useStorage(options), 521 | ls = _this$useStorage.ls, 522 | _options = _this$useStorage._options; 523 | 524 | Vue[_options.name] = ls; // eslint-disable-line 525 | 526 | Object.defineProperty(Vue.prototype || Vue.config.globalProperties, "$".concat(_options.name), { 527 | /** 528 | * Define $ls property 529 | * 530 | * @return {WebStorage} 531 | */ 532 | get: function get() { 533 | return ls; 534 | } 535 | }); 536 | } 537 | }; // eslint-disable-next-line 538 | 539 | _global.VueStorage = VueStorage; 540 | 541 | return VueStorage; 542 | 543 | }))); 544 | -------------------------------------------------------------------------------- /dist/vue-ls.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).VueStorage=t()}(this,function(){"use strict";function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t){for(var n=0;n=(new Date).getTime())return o.value;this.remove(e)}catch(e){return t}return t}},{key:"key",value:function(e){return this.storage.key(e)}},{key:"remove",value:function(e){return this.storage.removeItem(this.options.namespace+e)}},{key:"clear",value:function(){if(0!==this.length){for(var e=[],t=0;t 2 | 3 | 4 | 5 | 6 | 7 | Vue-ls Synchronized counter example 8 | 9 | 10 |

Synchronized tab counter

11 |
12 | localStorage: {{localCounter}}
13 | sessionCounter: {{sessionCounter}} 14 |
15 | 16 | 17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const http = require('http'); 3 | const connect = require('connect'); 4 | const serveStatic = require('serve-static'); 5 | const nightwatch = require('gulp-nightwatch'); 6 | const gutil = require('gulp-util'); 7 | const args = require('get-gulp-args')(); 8 | 9 | let httpServer; 10 | 11 | function logger (message) { 12 | gutil.log(gutil.colors.green(message)); 13 | } 14 | 15 | gulp.task('http:start', (done) => { 16 | logger('Start http server'); 17 | 18 | const app = connect().use(serveStatic('./')); 19 | httpServer = http.createServer(app).listen(9000, done); 20 | }); 21 | 22 | gulp.task('http:stop', (done) => { 23 | httpServer.close(); 24 | logger('Shutdown http server'); 25 | done(); 26 | }); 27 | 28 | gulp.task('e2e', gulp.series(['http:start'], () => { 29 | const env = args.env; 30 | 31 | return gulp.src('./build/nightwatch.config.js') 32 | .pipe(nightwatch({ 33 | configFile: './build/nightwatch.config.js', 34 | cliArgs: ['--env ' + env] 35 | })); 36 | })); 37 | 38 | gulp.task('test', gulp.series(['e2e', 'http:stop'])); 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-ls", 3 | "version": "4.2.0", 4 | "description": "Vue plugin for work with LocalStorage from Vue context", 5 | "main": "dist/vue-ls.js", 6 | "jsnext:main": "src/index.js", 7 | "unpkg": "dist/vue-ls.min.js", 8 | "files": [ 9 | "src", 10 | "dist/*.js" 11 | ], 12 | "scripts": { 13 | "build": "rollup -c ./build/rollup.config.js --name VueStorage && NODE_ENV=production rollup -c ./build/rollup.config.js --name VueStorage", 14 | "test": "NODE_ENV=test nyc ava", 15 | "test:browserstack": "node ./build/local.runner.js -c ./build/nightwatch.browserstack.config.js", 16 | "test:browserstack:chrome": "npm run test:browserstack --env bstack_chrome", 17 | "test:browserstack:edge": "npm run test:browserstack --env bstack_edge", 18 | "test:browserstack:firefox": "npm run test:browserstack --env bstack_firefox,bstack_firefox_osx", 19 | "test:browserstack:safari": "npm run test:browserstack --env bstack_safari", 20 | "test:browserstack:all": "npm run test:browserstack -- --env bstack_safari,bstack_chrome,bstack_firefox,bstack_firefox_osx,bstack_edge", 21 | "test:e2e": "gulp test --env chrome", 22 | "test:unit": "karma start build/karma.dev.config.js", 23 | "test:sauce": "karma start build/karma.sauce.config.js", 24 | "lint": "eslint ./", 25 | "report": "npm test && nyc report --reporter=html", 26 | "coveralls": "nyc report --reporter=text-lcov | coveralls", 27 | "codecov": "nyc report --reporter=lcovonly && codecov -t $CODECOV_TOKEN", 28 | "postinstall": "opencollective-postinstall || true" 29 | }, 30 | "keywords": [ 31 | "storage", 32 | "localstorage", 33 | "local-storage", 34 | "vue-localstorage", 35 | "vuejs-localstorage", 36 | "vue-local-storage", 37 | "vuejs-local-storage", 38 | "memorystroage", 39 | "sessionstorage", 40 | "session-storage", 41 | "vue-sessionstorage", 42 | "vuejs-sessionstorage", 43 | "vue-session-storage", 44 | "vuejs-session-storage", 45 | "memory-stroage", 46 | "vue-ls", 47 | "vue", 48 | "vuejs", 49 | "vue-plugin", 50 | "watch", 51 | "es6-modules" 52 | ], 53 | "repository": { 54 | "type": "git", 55 | "url": "git+https://github.com/RobinCK/vue-ls.git" 56 | }, 57 | "author": "Igor Ognichenko ", 58 | "bugs": { 59 | "url": "https://github.com/RobinCK/vue-ls/issues" 60 | }, 61 | "engines": { 62 | "node": ">=6.11.5" 63 | }, 64 | "homepage": "https://github.com/RobinCK/vue-ls#readme", 65 | "license": "MIT", 66 | "devDependencies": { 67 | "@ava/babel": "^1.0.1", 68 | "@babel/core": "^7.12.10", 69 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 70 | "@babel/plugin-transform-modules-commonjs": "^7.12.1", 71 | "@babel/plugin-transform-runtime": "^7.12.10", 72 | "@babel/polyfill": "^7.12.1", 73 | "@babel/preset-env": "^7.12.11", 74 | "@babel/register": "^7.12.10", 75 | "@babel/runtime": "^7.12.5", 76 | "ava": "^3.15.0", 77 | "babel-eslint": "^10.1.0", 78 | "babel-loader": "^8.2.2", 79 | "browserstack-local": "^1.4.8", 80 | "chromedriver": "^87.0.5", 81 | "connect": "^3.7.0", 82 | "coveralls": "^3.1.0", 83 | "eslint": "^7.17.0", 84 | "eslint-config-airbnb-base": "^14.2.1", 85 | "eslint-plugin-import": "^2.22.1", 86 | "eslint-plugin-node": "^11.1.0", 87 | "get-gulp-args": "^0.0.1", 88 | "gulp": "4.0.2", 89 | "gulp-nightwatch": "1.1.0", 90 | "gulp-util": "^3.0.8", 91 | "husky": "^4.3.7", 92 | "jasmine": "^3.6.3", 93 | "jasmine-core": "^3.6.0", 94 | "karma": "^5.2.3", 95 | "karma-chrome-launcher": "^3.1.0", 96 | "karma-jasmine": "^4.0.1", 97 | "karma-sauce-launcher": "^4.3.4", 98 | "karma-webpack": "^4.0.2", 99 | "mock-browser": "^0.92.14", 100 | "nightwatch": "^1.5.1", 101 | "nyc": "^15.1.0", 102 | "rollup": "^2.36.1", 103 | "rollup-plugin-babel": "^4.4.0", 104 | "rollup-plugin-local-resolve": "^1.0.7", 105 | "rollup-plugin-uglify": "^6.0.4", 106 | "selenium-server": "^3.14.0", 107 | "serve-static": "^1.14.1", 108 | "trim-right": "^1.0.1", 109 | "vue": "^2.6.12", 110 | "webpack": "^4.29.3" 111 | }, 112 | "semistandard": { 113 | "ignore": [ 114 | "node_modules", 115 | "bower_components", 116 | "build", 117 | "dist", 118 | "test" 119 | ] 120 | }, 121 | "ava": { 122 | "babel": { 123 | "extensions": [ 124 | "js" 125 | ] 126 | }, 127 | "tap": true, 128 | "files": [ 129 | "./test/unit/ava/*.js" 130 | ], 131 | "require": [ 132 | "@babel/register", 133 | "@babel/polyfill" 134 | ] 135 | }, 136 | "dependencies": { 137 | "opencollective-postinstall": "^2.0.2" 138 | }, 139 | "collective": { 140 | "type": "opencollective", 141 | "url": "https://opencollective.com/vue-ls" 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { MemoryStorage, WebStorage } from './storage'; 2 | 3 | // eslint-disable-next-line 4 | const _global = (typeof window !== 'undefined' ? window : global || {}); 5 | 6 | /** 7 | * @type {{install: (function(Object, Object): WebStorage)}} 8 | */ 9 | const VueStorage = { 10 | /** 11 | * use storage 12 | * 13 | * @param {Object} options 14 | * @returns {WebStorage} 15 | */ 16 | useStorage(options) { 17 | const _options = { 18 | ...options, 19 | storage: options.storage || 'local', 20 | name: options.name || 'ls', 21 | }; 22 | 23 | if (_options.storage && ['memory', 'local', 'session'].indexOf(_options.storage) === -1) { 24 | throw new Error(`Vue-ls: Storage "${_options.storage}" is not supported`); 25 | } 26 | 27 | let store = null; 28 | 29 | switch(_options.storage) { // eslint-disable-line 30 | case 'local': 31 | try { 32 | store = 'localStorage' in _global 33 | ? _global.localStorage 34 | : null 35 | ; 36 | } catch (e) { 37 | // In some situations the browser will 38 | // throw a security exception when attempting to access 39 | } 40 | break; 41 | 42 | case 'session': 43 | try { 44 | store = 'sessionStorage' in _global 45 | ? _global.sessionStorage 46 | : null 47 | ; 48 | } catch (e) { 49 | // In some situations the browser will 50 | // throw a security exception when attempting to access 51 | } 52 | break; 53 | case 'memory': 54 | store = MemoryStorage; 55 | break; 56 | } 57 | 58 | if (!store) { 59 | store = MemoryStorage; 60 | // eslint-disable-next-line 61 | console.error(`Vue-ls: Storage "${_options.storage}" is not supported your system, use memory storage`); 62 | } 63 | 64 | const ls = new WebStorage(store); 65 | 66 | ls.setOptions(Object.assign(ls.options, { 67 | namespace: '', 68 | }, _options || {})); 69 | 70 | return { ls, _options }; 71 | }, 72 | /** 73 | * Install plugin 74 | * 75 | * @param {Object} Vue 76 | * @param {Object} options 77 | * @returns {WebStorage} 78 | */ 79 | install(Vue, options = {}) { 80 | const { ls, _options } = this.useStorage(options); 81 | Vue[_options.name] = ls; // eslint-disable-line 82 | 83 | Object.defineProperty(Vue.prototype || Vue.config.globalProperties, `$${_options.name}`, { 84 | /** 85 | * Define $ls property 86 | * 87 | * @return {WebStorage} 88 | */ 89 | get() { 90 | return ls; 91 | }, 92 | }); 93 | }, 94 | }; 95 | 96 | // eslint-disable-next-line 97 | _global.VueStorage = VueStorage; 98 | 99 | export default VueStorage; 100 | -------------------------------------------------------------------------------- /src/storage/MemoryStorage.js: -------------------------------------------------------------------------------- 1 | /* eslint class-methods-use-this: off */ 2 | 3 | let ls = {}; 4 | 5 | class MemoryStorageInterface { 6 | constructor() { 7 | Object.defineProperty(this, 'length', { 8 | /** 9 | * Define length property 10 | * 11 | * @return {number} 12 | */ 13 | get() { 14 | return Object.keys(ls).length; 15 | }, 16 | }); 17 | } 18 | 19 | /** 20 | * Get item 21 | * 22 | * @param {string} name 23 | * @returns {*} 24 | */ 25 | getItem(name) { 26 | return name in ls ? ls[name] : null; 27 | } 28 | 29 | /** 30 | * Set item 31 | * 32 | * @param {string} name 33 | * @param {*} value 34 | * @returns {boolean} 35 | */ 36 | setItem(name, value) { 37 | ls[name] = value; 38 | 39 | return true; 40 | } 41 | 42 | /** 43 | * Remove item 44 | * 45 | * @param {string} name 46 | * @returns {boolean} 47 | */ 48 | removeItem(name) { 49 | const found = name in ls; 50 | 51 | if (found) { 52 | return delete ls[name]; 53 | } 54 | 55 | return false; 56 | } 57 | 58 | /** 59 | * Clear storage 60 | * 61 | * @returns {boolean} 62 | */ 63 | clear() { 64 | ls = {}; 65 | 66 | return true; 67 | } 68 | 69 | /** 70 | * Get item by key 71 | * 72 | * @param {number} index 73 | * @returns {*} 74 | */ 75 | key(index) { 76 | const keys = Object.keys(ls); 77 | 78 | return typeof keys[index] !== 'undefined' ? keys[index] : null; 79 | } 80 | } 81 | 82 | const MemoryStorage = new MemoryStorageInterface(); 83 | 84 | export { MemoryStorage }; 85 | -------------------------------------------------------------------------------- /src/storage/WebStorage.js: -------------------------------------------------------------------------------- 1 | import { WebStorageEvent } from './WebStorageEvent'; 2 | 3 | /** 4 | * Storage Bridge 5 | */ 6 | export class WebStorage { 7 | /** 8 | * @param {Object} storage 9 | */ 10 | constructor(storage) { 11 | this.storage = storage; 12 | this.options = { 13 | namespace: '', 14 | events: ['storage'], 15 | }; 16 | 17 | Object.defineProperty(this, 'length', { 18 | /** 19 | * Define length property 20 | * 21 | * @return {number} 22 | */ 23 | get() { 24 | return this.storage.length; 25 | }, 26 | }); 27 | 28 | if (typeof window !== 'undefined') { 29 | for (const i in this.options.events) { 30 | if (window.addEventListener) { 31 | window.addEventListener(this.options.events[i], WebStorageEvent.emit, false); 32 | } else if (window.attachEvent) { 33 | window.attachEvent(`on${this.options.events[i]}`, WebStorageEvent.emit); 34 | } else { 35 | window[`on${this.options.events[i]}`] = WebStorageEvent.emit; 36 | } 37 | } 38 | } 39 | } 40 | 41 | /** 42 | * Set Options 43 | * 44 | * @param {Object} options 45 | */ 46 | setOptions(options = {}) { 47 | this.options = Object.assign(this.options, options); 48 | } 49 | 50 | /** 51 | * Set item 52 | * 53 | * @param {string} name 54 | * @param {*} value 55 | * @param {number} expire - seconds 56 | */ 57 | set(name, value, expire = null) { 58 | const stringifyValue = JSON.stringify({ 59 | value, 60 | expire: expire !== null ? new Date().getTime() + expire : null, 61 | }); 62 | 63 | this.storage.setItem(this.options.namespace + name, stringifyValue); 64 | } 65 | 66 | /** 67 | * Get item 68 | * 69 | * @param {string} name 70 | * @param {*} def - default value 71 | * @returns {*} 72 | */ 73 | get(name, def = null) { 74 | const item = this.storage.getItem(this.options.namespace + name); 75 | 76 | if (item !== null) { 77 | try { 78 | const data = JSON.parse(item); 79 | 80 | if (data.expire === null) { 81 | return data.value; 82 | } 83 | 84 | if (data.expire >= new Date().getTime()) { 85 | return data.value; 86 | } 87 | 88 | this.remove(name); 89 | } catch (err) { 90 | return def; 91 | } 92 | } 93 | 94 | return def; 95 | } 96 | 97 | /** 98 | * Get item by key 99 | * 100 | * @param {number} index 101 | * @return {*} 102 | */ 103 | key(index) { 104 | return this.storage.key(index); 105 | } 106 | 107 | /** 108 | * Remove item 109 | * 110 | * @param {string} name 111 | * @return {boolean} 112 | */ 113 | remove(name) { 114 | return this.storage.removeItem(this.options.namespace + name); 115 | } 116 | 117 | /** 118 | * Clear storage 119 | */ 120 | clear() { 121 | if (this.length === 0) { 122 | return; 123 | } 124 | 125 | const removedKeys = []; 126 | 127 | for (let i = 0; i < this.length; i++) { 128 | const key = this.storage.key(i); 129 | const regexp = new RegExp(`^${this.options.namespace}.+`, 'i'); 130 | 131 | if (regexp.test(key) === false) { 132 | continue; 133 | } 134 | 135 | removedKeys.push(key); 136 | } 137 | 138 | for (const key in removedKeys) { 139 | this.storage.removeItem(removedKeys[key]); 140 | } 141 | } 142 | 143 | /** 144 | * Add storage change event 145 | * 146 | * @param {string} name 147 | * @param {Function} callback 148 | */ 149 | on(name, callback) { 150 | WebStorageEvent.on(this.options.namespace + name, callback); 151 | } 152 | 153 | /** 154 | * Remove storage change event 155 | * 156 | * @param {string} name 157 | * @param {Function} callback 158 | */ 159 | off(name, callback) { 160 | WebStorageEvent.off(this.options.namespace + name, callback); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/storage/WebStorageEvent.js: -------------------------------------------------------------------------------- 1 | const listeners = {}; 2 | 3 | /** 4 | * Event class 5 | */ 6 | export class WebStorageEvent { 7 | /** 8 | * Add storage change event 9 | * 10 | * @param {string} name 11 | * @param {Function} callback 12 | */ 13 | static on(name, callback) { 14 | if (typeof listeners[name] === 'undefined') { 15 | listeners[name] = []; 16 | } 17 | 18 | listeners[name].push(callback); 19 | } 20 | 21 | /** 22 | * Remove storage change event 23 | * 24 | * @param {string} name 25 | * @param {Function} callback 26 | */ 27 | static off(name, callback) { 28 | if (listeners[name].length) { 29 | listeners[name].splice(listeners[name].indexOf(callback), 1); 30 | } else { 31 | listeners[name] = []; 32 | } 33 | } 34 | 35 | /** 36 | * Emit event 37 | * 38 | * @param {Object} event 39 | */ 40 | static emit(event) { 41 | const e = event || window.event; 42 | 43 | const getValue = (data) => { 44 | try { 45 | return JSON.parse(data).value; 46 | } catch (err) { 47 | return data; 48 | } 49 | }; 50 | 51 | const fire = (listener) => { 52 | const newValue = getValue(e.newValue); 53 | const oldValue = getValue(e.oldValue); 54 | 55 | listener(newValue, oldValue, e.url || e.uri); 56 | }; 57 | 58 | if (typeof e === 'undefined' || typeof e.key === 'undefined') { 59 | return; 60 | } 61 | 62 | const all = listeners[e.key]; 63 | 64 | if (typeof all !== 'undefined') { 65 | all.forEach(fire); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/storage/index.js: -------------------------------------------------------------------------------- 1 | export * from './MemoryStorage'; 2 | export * from './WebStorage'; 3 | export * from './WebStorageEvent'; 4 | -------------------------------------------------------------------------------- /test/e2e/counter.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'Counter test' : function (browser) { 3 | browser 4 | .url("http://localhost:9000/examples/counter/index.html") 5 | .assert.containsText("#count", "0", "Start value: 0") 6 | .click('#increment') 7 | .assert.containsText("#count", "1", "Value after increment: 0 -> 1") 8 | .click('#increment') 9 | .assert.containsText("#count", "2", "Value after increment: 1 -> 2") 10 | .click('#decrement') 11 | .assert.containsText("#count", "1", "Value after decrement: 2 -> 1") 12 | .end(); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /test/unit/ava/eventAttach.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import test from 'ava'; 3 | 4 | import './helpers/setupIEBrowserEnv' 5 | import Ls from '../../../src/index'; 6 | import { WebStorageEvent } from '../../../src/storage'; 7 | 8 | Vue.use(Ls); 9 | 10 | test('Add/Remove event', (t) => { 11 | t.plan(2); 12 | 13 | Vue.ls.on('item_one_test', () => {}); 14 | Vue.ls.on('item_two_test', () => {}); 15 | Vue.ls.on('item_two_test', () => {}); 16 | Vue.ls.on('item_three_test', (val, oldVal) => { 17 | t.is(val, 'val'); 18 | t.is(oldVal, 'old_val'); 19 | }); 20 | Vue.ls.off('item_two_test', () => {}); 21 | Vue.ls.off('item_one_test', () => {}); 22 | 23 | WebStorageEvent.emit({ 24 | key: 'item_three_test', 25 | newValue: JSON.stringify({ value: 'val', expire: null }), 26 | oldValue: JSON.stringify({ value: 'old_val', expire: null }), 27 | }); 28 | WebStorageEvent.emit({ 29 | key: 'item_undefined_test', 30 | newValue: JSON.stringify({ value: 'val', expire: null }), 31 | oldValue: JSON.stringify({ value: 'old_val', expire: null }), 32 | }); 33 | WebStorageEvent.emit(); 34 | }); 35 | 36 | -------------------------------------------------------------------------------- /test/unit/ava/eventWindow.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import test from 'ava'; 3 | 4 | import './helpers/setupOldBrowserEnv' 5 | import Ls from '../../../src/index'; 6 | import { WebStorageEvent } from '../../../src/storage'; 7 | 8 | Vue.use(Ls); 9 | 10 | test('Add/Remove event', (t) => { 11 | t.plan(2); 12 | 13 | Vue.ls.on('item_one_test', () => {}); 14 | Vue.ls.on('item_two_test', () => {}); 15 | Vue.ls.on('item_two_test', () => {}); 16 | Vue.ls.on('item_three_test', (val, oldVal) => { 17 | t.is(val, 'val'); 18 | t.is(oldVal, 'old_val'); 19 | }); 20 | Vue.ls.off('item_two_test', () => {}); 21 | Vue.ls.off('item_one_test', () => {}); 22 | 23 | WebStorageEvent.emit({ 24 | key: 'item_three_test', 25 | newValue: JSON.stringify({ value: 'val', expire: null }), 26 | oldValue: JSON.stringify({ value: 'old_val', expire: null }), 27 | }); 28 | WebStorageEvent.emit({ 29 | key: 'item_undefined_test', 30 | newValue: JSON.stringify({ value: 'val', expire: null }), 31 | oldValue: JSON.stringify({ value: 'old_val', expire: null }), 32 | }); 33 | WebStorageEvent.emit(); 34 | }); 35 | 36 | -------------------------------------------------------------------------------- /test/unit/ava/events.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import test from 'ava'; 3 | 4 | import './helpers/setupBrowserEnv' 5 | import Ls from '../../../src/index'; 6 | import { WebStorageEvent } from '../../../src/storage'; 7 | 8 | Vue.use(Ls); 9 | 10 | test.beforeEach(() => { 11 | window.localStorage.clear(); 12 | }); 13 | 14 | //mock-browser not supported storage event 15 | test('Add/Remove event', (t) => { 16 | t.plan(4); 17 | 18 | Vue.ls.on('item_one_test', () => {}); 19 | Vue.ls.on('item_two_test', () => {}); 20 | Vue.ls.on('item_two_test', () => {}); 21 | Vue.ls.on('item_three_test', (val, oldVal) => { 22 | t.is(val, 'val'); 23 | t.is(oldVal, 'old_val'); 24 | }); 25 | Vue.ls.off('item_two_test', () => {}); 26 | Vue.ls.off('item_one_test', () => {}); 27 | Vue.ls.off('item_one_test', () => {}); 28 | 29 | WebStorageEvent.emit({ 30 | key: 'item_three_test', 31 | newValue: JSON.stringify({ value: 'val', expire: null }), 32 | oldValue: JSON.stringify({ value: 'old_val', expire: null }), 33 | }); 34 | WebStorageEvent.emit({ 35 | key: 'item_undefined_test', 36 | newValue: JSON.stringify({ value: 'val', expire: null }), 37 | oldValue: JSON.stringify({ value: 'old_val', expire: null }), 38 | }); 39 | WebStorageEvent.emit({ 40 | key: 'item_three_test', 41 | newValue: 'val', 42 | oldValue: 'old_val', 43 | }); 44 | WebStorageEvent.emit(); 45 | }); 46 | 47 | -------------------------------------------------------------------------------- /test/unit/ava/helpers/setupBrowserEnv.js: -------------------------------------------------------------------------------- 1 | const MockBrowser = require('mock-browser').mocks.MockBrowser; 2 | 3 | global.document = MockBrowser.createDocument(); 4 | global.window = MockBrowser.createWindow(); 5 | -------------------------------------------------------------------------------- /test/unit/ava/helpers/setupIEBrowserEnv.js: -------------------------------------------------------------------------------- 1 | const MockBrowser = require('mock-browser').mocks.MockBrowser; 2 | 3 | global.document = MockBrowser.createDocument(); 4 | global.window = MockBrowser.createWindow(); 5 | global.window.addEventListener = null; 6 | global.window.attachEvent = function(name, callback) { 7 | callback.call(); 8 | }; 9 | -------------------------------------------------------------------------------- /test/unit/ava/helpers/setupOldBrowserEnv.js: -------------------------------------------------------------------------------- 1 | const MockBrowser = require('mock-browser').mocks.MockBrowser; 2 | 3 | global.document = MockBrowser.createDocument(); 4 | global.window = MockBrowser.createWindow(); 5 | global.window.addEventListener = null; 6 | -------------------------------------------------------------------------------- /test/unit/ava/localStorage.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import test from 'ava'; 3 | 4 | import './helpers/setupBrowserEnv' 5 | import Ls from '../../../src/index'; 6 | 7 | const namespace = 'test__'; 8 | 9 | Vue.use(Ls, { 10 | namespace: namespace, 11 | storage: 'local', 12 | }); 13 | 14 | test.beforeEach(() => { 15 | window.localStorage.clear(); 16 | }); 17 | 18 | test('Set item', (t) => { 19 | window.localStorage.clear(); // fix for ava-beta-8 20 | Vue.ls.set('set_item_test', 'val'); 21 | 22 | t.is(JSON.parse(window.localStorage.getItem(`${namespace}set_item_test`)).value, 'val'); 23 | }); 24 | 25 | test('Get key by index', (t) => { 26 | window.localStorage.clear(); // fix for ava-beta-8 27 | Vue.ls.set('key_item_one_test', 'val_one'); 28 | Vue.ls.set('key_item_two_test', 'val_two'); 29 | 30 | t.is(Vue.ls.key(1), `${namespace}key_item_two_test`); 31 | }); 32 | 33 | test('Get item', (t) => { 34 | window.localStorage.clear(); // fix for ava-beta-8 35 | window.localStorage.setItem(`${namespace}get_item_test`, JSON.stringify({value: 'val', expire: null})); 36 | 37 | t.is(Vue.ls.get('get_item_test'), 'val'); 38 | }); 39 | 40 | test('Get item try', (t) => { 41 | window.localStorage.clear(); // fix for ava-beta-8 42 | window.localStorage.setItem(`${namespace}get_item_test`, ';'); 43 | 44 | t.is(Vue.ls.get('get_item_test', 1), 1); 45 | }); 46 | 47 | test('Get default value', (t) => { 48 | window.localStorage.clear(); // fix for ava-beta-8 49 | t.is(Vue.ls.get('undefined_item_test', 10), 10); 50 | }); 51 | 52 | test('Expired item', (t) => { 53 | window.localStorage.clear(); // fix for ava-beta-8 54 | Vue.ls.set('expired_item_test', 'val', -1); 55 | 56 | t.is(Vue.ls.get('expired_item_test'), null); 57 | }); 58 | 59 | test('Not expired item', (t) => { 60 | window.localStorage.clear(); // fix for ava-beta-8 61 | Vue.ls.set('expired_item_test', 'val', 1); 62 | 63 | t.is(Vue.ls.get('expired_item_test'), 'val'); 64 | }); 65 | 66 | test('Remove item', (t) => { 67 | window.localStorage.clear(); // fix for ava-beta-8 68 | window.localStorage.setItem(`${namespace}remove_item_test`, JSON.stringify({value: 'val', expire: null})); 69 | Vue.ls.remove('remove_item_test'); 70 | 71 | t.is(window.localStorage.getItem(`${namespace}remove_item_test`), null); 72 | }); 73 | 74 | test('Clear', (t) => { 75 | window.localStorage.clear(); // fix for ava-beta-8 76 | Vue.ls.set('item_test', 'val'); 77 | Vue.ls.clear(); 78 | 79 | t.is(Vue.ls.get('item_test'), null); 80 | }); 81 | 82 | test('Empty clear', (t) => { 83 | window.localStorage.clear(); // fix for ava-beta-8 84 | Vue.ls.clear(); 85 | t.is(Vue.ls.length, 0); 86 | }); 87 | 88 | test('Clear namespace', (t) => { 89 | window.localStorage.clear(); // fix for ava-beta-8 90 | t.plan(2); 91 | 92 | Vue.ls.set('item_test', 'val'); 93 | window.localStorage.setItem('item_test', JSON.stringify({value: 'val', expire: null})); 94 | 95 | Vue.ls.clear(); 96 | 97 | t.is(Vue.ls.get('item_test'), null); 98 | t.is(JSON.parse(window.localStorage.getItem(`item_test`)).value, 'val'); 99 | }); 100 | 101 | test('Get length', (t) => { 102 | window.localStorage.clear(); // fix for ava-beta-8 103 | Vue.ls.set('item_one_test', 'val'); 104 | Vue.ls.set('item_two_test', 'val'); 105 | 106 | t.is(Vue.ls.length, 2); 107 | }); 108 | 109 | test('Serialized data', (t) => { 110 | window.localStorage.clear(); // fix for ava-beta-8 111 | t.plan(5); 112 | 113 | Vue.ls.set('item_object', {foo: 'boo'}); 114 | Vue.ls.set('item_array', [2, 3, 4, 5]); 115 | Vue.ls.set('item_number', 6); 116 | Vue.ls.set('item_string', '7'); 117 | Vue.ls.set('item_boolean', false); 118 | 119 | t.deepEqual(Vue.ls.get('item_object'), {foo: 'boo'}); 120 | t.deepEqual(Vue.ls.get('item_array'), [2, 3, 4, 5]); 121 | t.is(Vue.ls.get('item_number'), 6); 122 | t.is(Vue.ls.get('item_string'), '7'); 123 | t.is(Vue.ls.get('item_boolean'), false); 124 | }); 125 | 126 | //mock-browser not supported storage event 127 | test('Add/Remove event', (t) => { 128 | window.localStorage.clear(); // fix for ava-beta-8 129 | Vue.ls.on('item_one_test', () => {}); 130 | Vue.ls.on('item_two_test', () => {}); 131 | Vue.ls.on('item_two_test', () => {}); 132 | Vue.ls.off('item_two_test', () => {}); 133 | Vue.ls.off('item_one_test', () => {}); 134 | t.is(true, true); 135 | }); 136 | 137 | test('Plugin context', (t) => { 138 | window.localStorage.clear(); // fix for ava-beta-8 139 | new Vue({ 140 | created () { 141 | this.$ls.set('item_test', 'val'); 142 | 143 | t.is(this.$ls.get('item_test'), 'val'); 144 | } 145 | }); 146 | }); 147 | -------------------------------------------------------------------------------- /test/unit/ava/memoryFallback.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import test from 'ava'; 3 | 4 | import Ls from '../../../src/index'; 5 | 6 | const namespace = 'test__'; 7 | 8 | Vue.use(Ls, { 9 | namespace: namespace 10 | }); 11 | 12 | test.beforeEach(() => { 13 | Vue.ls.storage.clear(); 14 | }); 15 | 16 | test('Set/Get item', (t) => { 17 | Vue.ls.storage.clear(); // fix for ava-beta-8 18 | Vue.ls.set('set_item_test', 'val'); 19 | 20 | t.is(Vue.ls.get('set_item_test'), 'val'); 21 | }); 22 | 23 | test('Remove item', (t) => { 24 | Vue.ls.storage.clear(); // fix for ava-beta-8 25 | Vue.ls.set('remove_item_test', 'val'); 26 | Vue.ls.remove('remove_item_test'); 27 | 28 | t.is(Vue.ls.get('remove_item_test'), null); 29 | }); 30 | 31 | test('Removing non-existent itset_item_testen', (t) => { 32 | Vue.ls.storage.clear(); // fix for ava-beta-8 33 | t.is(Vue.ls.remove('remove_item_test'), false); 34 | }); 35 | 36 | test('Get key by index', (t) => { 37 | Vue.ls.storage.clear(); // fix for ava-beta-8 38 | t.plan(2); 39 | 40 | Vue.ls.set('key_item_one_test', 'val_one'); 41 | Vue.ls.set('key_item_two_test', 'val_two'); 42 | 43 | t.is(Vue.ls.key(1), `${namespace}key_item_two_test`); 44 | t.is(Vue.ls.key(100), null); 45 | }); 46 | 47 | test('Clear', (t) => { 48 | Vue.ls.storage.clear(); // fix for ava-beta-8 49 | Vue.ls.set('item_test', 'val'); 50 | Vue.ls.clear(); 51 | 52 | t.is(Vue.ls.get('item_test'), null); 53 | }); 54 | -------------------------------------------------------------------------------- /test/unit/ava/memoryStorage.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import test from 'ava'; 3 | 4 | import './helpers/setupBrowserEnv' 5 | import Ls from '../../../src/index'; 6 | 7 | const namespace = 'test__'; 8 | 9 | Vue.use(Ls, { 10 | namespace: namespace, 11 | storage: 'memory', 12 | name: 'memory' 13 | }); 14 | 15 | test.beforeEach(() => { 16 | Vue.memory.storage.clear(); 17 | }); 18 | 19 | test('Set item', (t) => { 20 | Vue.memory.storage.clear(); // fix for ava-beta-8 21 | Vue.memory.set('set_item_test', 'val'); 22 | 23 | t.is(JSON.parse(Vue.memory.storage.getItem(`${namespace}set_item_test`)).value, 'val'); 24 | }); 25 | 26 | test('Get key by index', (t) => { 27 | Vue.memory.storage.clear(); // fix for ava-beta-8 28 | Vue.memory.set('key_item_one_test', 'val_one'); 29 | Vue.memory.set('key_item_two_test', 'val_two'); 30 | 31 | t.is(Vue.memory.key(1), `${namespace}key_item_two_test`); 32 | }); 33 | 34 | test('Get item', (t) => { 35 | Vue.memory.storage.clear(); // fix for ava-beta-8 36 | Vue.memory.storage.setItem(`${namespace}get_item_test`, JSON.stringify({value: 'val', expire: null})); 37 | 38 | t.is(Vue.memory.get('get_item_test'), 'val'); 39 | }); 40 | 41 | test('Get item try', (t) => { 42 | Vue.memory.storage.clear(); // fix for ava-beta-8 43 | Vue.memory.storage.setItem(`${namespace}get_item_test`, ';'); 44 | 45 | t.is(Vue.memory.get('get_item_test', 1), 1); 46 | }); 47 | 48 | test('Get default value', (t) => { 49 | Vue.memory.storage.clear(); // fix for ava-beta-8 50 | t.is(Vue.memory.get('undefined_item_test', 10), 10); 51 | }); 52 | 53 | test('Expired item', (t) => { 54 | Vue.memory.storage.clear(); // fix for ava-beta-8 55 | Vue.memory.set('expired_item_test', 'val', -1); 56 | 57 | t.is(Vue.memory.get('expired_item_test'), null); 58 | }); 59 | 60 | test('Not expired item', (t) => { 61 | Vue.memory.storage.clear(); // fix for ava-beta-8 62 | Vue.memory.set('expired_item_test', 'val', 1); 63 | 64 | t.is(Vue.memory.get('expired_item_test'), 'val'); 65 | }); 66 | 67 | test('Remove item', (t) => { 68 | Vue.memory.storage.clear(); // fix for ava-beta-8 69 | Vue.memory.storage.setItem(`${namespace}remove_item_test`, JSON.stringify({value: 'val', expire: null})); 70 | Vue.memory.remove('remove_item_test'); 71 | 72 | t.is(Vue.memory.storage.getItem(`${namespace}remove_item_test`), null); 73 | }); 74 | 75 | test('Clear', (t) => { 76 | Vue.memory.storage.clear(); // fix for ava-beta-8 77 | Vue.memory.set('item_test', 'val'); 78 | Vue.memory.clear(); 79 | 80 | t.is(Vue.memory.get('item_test'), null); 81 | }); 82 | 83 | test('Empty clear', (t) => { 84 | Vue.memory.storage.clear(); // fix for ava-beta-8 85 | Vue.memory.clear(); 86 | t.is(Vue.memory.length, 0); 87 | }); 88 | 89 | test('Clear namespace', (t) => { 90 | Vue.memory.storage.clear(); // fix for ava-beta-8 91 | t.plan(2); 92 | 93 | Vue.memory.set('item_test', 'val'); 94 | Vue.memory.storage.setItem('item_test', JSON.stringify({value: 'val', expire: null})); 95 | 96 | Vue.memory.clear(); 97 | 98 | t.is(Vue.memory.get('item_test'), null); 99 | t.is(JSON.parse(Vue.memory.storage.getItem(`item_test`)).value, 'val'); 100 | }); 101 | 102 | test('Get length', (t) => { 103 | Vue.memory.storage.clear(); // fix for ava-beta-8 104 | Vue.memory.set('item_one_test', 'val'); 105 | Vue.memory.set('item_two_test', 'val'); 106 | 107 | t.is(Vue.memory.length, 2); 108 | }); 109 | 110 | test('Serialized data', (t) => { 111 | Vue.memory.storage.clear(); // fix for ava-beta-8 112 | t.plan(5); 113 | 114 | Vue.memory.set('item_object', {foo: 'boo'}); 115 | Vue.memory.set('item_array', [2, 3, 4, 5]); 116 | Vue.memory.set('item_number', 6); 117 | Vue.memory.set('item_string', '7'); 118 | Vue.memory.set('item_boolean', false); 119 | 120 | t.deepEqual(Vue.memory.get('item_object'), {foo: 'boo'}); 121 | t.deepEqual(Vue.memory.get('item_array'), [2, 3, 4, 5]); 122 | t.is(Vue.memory.get('item_number'), 6); 123 | t.is(Vue.memory.get('item_string'), '7'); 124 | t.is(Vue.memory.get('item_boolean'), false); 125 | }); 126 | 127 | test('Plugin context', (t) => { 128 | Vue.memory.storage.clear(); // fix for ava-beta-8 129 | new Vue({ 130 | created () { 131 | this.$memory.set('item_test', 'val'); 132 | 133 | t.is(this.$memory.get('item_test'), 'val'); 134 | } 135 | }); 136 | }); 137 | -------------------------------------------------------------------------------- /test/unit/ava/sessionStorage.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import test from 'ava'; 3 | 4 | import './helpers/setupBrowserEnv' 5 | import Ls from '../../../src/index'; 6 | 7 | const namespace = 'test__'; 8 | 9 | Vue.use(Ls, { 10 | namespace: namespace, 11 | storage: 'session', 12 | name: 'session' 13 | }); 14 | 15 | test.beforeEach(() => { 16 | window.sessionStorage.clear(); 17 | }); 18 | 19 | test('Set item', (t) => { 20 | window.sessionStorage.clear(); // fix for ava-beta-8 21 | Vue.session.set('set_item_test', 'val'); 22 | 23 | t.is(JSON.parse(window.sessionStorage.getItem(`${namespace}set_item_test`)).value, 'val'); 24 | }); 25 | 26 | test('Get key by index', (t) => { 27 | window.sessionStorage.clear(); // fix for ava-beta-8 28 | Vue.session.set('key_item_one_test', 'val_one'); 29 | Vue.session.set('key_item_two_test', 'val_two'); 30 | 31 | t.is(Vue.session.key(1), `${namespace}key_item_two_test`); 32 | }); 33 | 34 | test('Get item', (t) => { 35 | window.sessionStorage.clear(); // fix for ava-beta-8 36 | window.sessionStorage.setItem(`${namespace}get_item_test`, JSON.stringify({value: 'val', expire: null})); 37 | 38 | t.is(Vue.session.get('get_item_test'), 'val'); 39 | }); 40 | 41 | test('Get item try', (t) => { 42 | window.sessionStorage.clear(); // fix for ava-beta-8 43 | window.sessionStorage.setItem(`${namespace}get_item_test`, ';'); 44 | 45 | t.is(Vue.session.get('get_item_test', 1), 1); 46 | }); 47 | 48 | test('Get default value', (t) => { 49 | window.sessionStorage.clear(); // fix for ava-beta-8 50 | t.is(Vue.session.get('undefined_item_test', 10), 10); 51 | }); 52 | 53 | test('Expired item', (t) => { 54 | window.sessionStorage.clear(); // fix for ava-beta-8 55 | Vue.session.set('expired_item_test', 'val', -1); 56 | 57 | t.is(Vue.session.get('expired_item_test'), null); 58 | }); 59 | 60 | test('Not expired item', (t) => { 61 | window.sessionStorage.clear(); // fix for ava-beta-8 62 | Vue.session.set('expired_item_test', 'val', 1); 63 | 64 | t.is(Vue.session.get('expired_item_test'), 'val'); 65 | }); 66 | 67 | test('Remove item', (t) => { 68 | window.sessionStorage.clear(); // fix for ava-beta-8 69 | window.sessionStorage.setItem(`${namespace}remove_item_test`, JSON.stringify({value: 'val', expire: null})); 70 | Vue.session.remove('remove_item_test'); 71 | 72 | t.is(window.sessionStorage.getItem(`${namespace}remove_item_test`), null); 73 | }); 74 | 75 | test('Clear', (t) => { 76 | window.sessionStorage.clear(); // fix for ava-beta-8 77 | Vue.session.set('item_test', 'val'); 78 | Vue.session.clear(); 79 | 80 | t.is(Vue.session.get('item_test'), null); 81 | }); 82 | 83 | test('Empty clear', (t) => { 84 | window.sessionStorage.clear(); // fix for ava-beta-8 85 | Vue.session.clear(); 86 | t.is(Vue.session.length, 0); 87 | }); 88 | 89 | test('Clear namespace', (t) => { 90 | window.sessionStorage.clear(); // fix for ava-beta-8 91 | t.plan(2); 92 | 93 | Vue.session.set('item_test', 'val'); 94 | window.sessionStorage.setItem('item_test', JSON.stringify({value: 'val', expire: null})); 95 | 96 | Vue.session.clear(); 97 | 98 | t.is(Vue.session.get('item_test'), null); 99 | t.is(JSON.parse(window.sessionStorage.getItem(`item_test`)).value, 'val'); 100 | }); 101 | 102 | test('Get length', (t) => { 103 | window.sessionStorage.clear(); // fix for ava-beta-8 104 | Vue.session.set('item_one_test', 'val'); 105 | Vue.session.set('item_two_test', 'val'); 106 | 107 | t.is(Vue.session.length, 2); 108 | }); 109 | 110 | test('Serialized data', (t) => { 111 | window.sessionStorage.clear(); // fix for ava-beta-8 112 | t.plan(5); 113 | 114 | Vue.session.set('item_object', {foo: 'boo'}); 115 | Vue.session.set('item_array', [2, 3, 4, 5]); 116 | Vue.session.set('item_number', 6); 117 | Vue.session.set('item_string', '7'); 118 | Vue.session.set('item_boolean', false); 119 | 120 | t.deepEqual(Vue.session.get('item_object'), {foo: 'boo'}); 121 | t.deepEqual(Vue.session.get('item_array'), [2, 3, 4, 5]); 122 | t.is(Vue.session.get('item_number'), 6); 123 | t.is(Vue.session.get('item_string'), '7'); 124 | t.is(Vue.session.get('item_boolean'), false); 125 | }); 126 | 127 | test('Plugin context', (t) => { 128 | window.sessionStorage.clear(); // fix for ava-beta-8 129 | new Vue({ 130 | created () { 131 | this.$session.set('item_test', 'val'); 132 | 133 | t.is(this.$session.get('item_test'), 'val'); 134 | } 135 | }); 136 | }); 137 | -------------------------------------------------------------------------------- /test/unit/ava/unknownStorage.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import test from 'ava'; 3 | 4 | import Ls from '../../../src/index'; 5 | 6 | test('Test exception storage', (t) => { 7 | try { 8 | Vue.use(Ls, { 9 | storage: 'unknown', 10 | }); 11 | } catch(err) { 12 | t.is(err.message, 'Vue-ls: Storage "unknown" is not supported'); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /test/unit/jasmine/index.js: -------------------------------------------------------------------------------- 1 | import Ls from '../../../src/index'; 2 | import Vue from 'vue'; 3 | 4 | const namespace = 'test__'; 5 | 6 | Vue.use(Ls, { 7 | namespace: namespace 8 | }); 9 | 10 | describe("Plugin test", () => { 11 | beforeEach(() => { 12 | window.localStorage.clear(); 13 | }); 14 | 15 | it("Set item", () => { 16 | Vue.ls.set('set_item_test', 'val'); 17 | 18 | expect(JSON.parse(window.localStorage.getItem(`${namespace}set_item_test`)).value).toBe('val'); 19 | }); 20 | 21 | it('Get item', () => { 22 | window.localStorage.setItem(`${namespace}get_item_test`, JSON.stringify({value: 'val', expire: null})); 23 | 24 | expect(Vue.ls.get('get_item_test')).toBe('val'); 25 | }); 26 | 27 | it('Get default value', () => { 28 | expect(Vue.ls.get('undefined_item_test', 10)).toBe(10); 29 | }); 30 | 31 | it('Expired item', () => { 32 | Vue.ls.set('expired_item_test', 'val', -1); 33 | 34 | expect(Vue.ls.get('expired_item_test')).toBe(null); 35 | }); 36 | 37 | it('Not expired item', () => { 38 | Vue.ls.set('expired_item_test', 'val', 1); 39 | 40 | expect(Vue.ls.get('expired_item_test')).toBe('val'); 41 | }); 42 | 43 | it('Remove item', () => { 44 | window.localStorage.setItem(`${namespace}remove_item_test`, JSON.stringify({value: 'val', expire: null})); 45 | Vue.ls.remove('remove_item_test'); 46 | 47 | expect(window.localStorage.getItem(`${namespace}remove_item_test`)).toBe(null); 48 | }); 49 | 50 | 51 | it('Clear', () => { 52 | Vue.ls.set('item_test', 'val'); 53 | Vue.ls.clear(); 54 | 55 | expect(Vue.ls.get('item_test')).toBe(null); 56 | }); 57 | 58 | it('Empty clear', () => { 59 | Vue.ls.clear(); 60 | expect(Vue.ls.length).toBe(0); 61 | }); 62 | 63 | it('Clear namespace', () => { 64 | Vue.ls.set('item_test', 'val'); 65 | window.localStorage.setItem('item_test', JSON.stringify({value: 'val', expire: null})); 66 | 67 | Vue.ls.clear(); 68 | 69 | expect(Vue.ls.get('item_test')).toBe(null); 70 | expect(JSON.parse(window.localStorage.getItem(`item_test`)).value).toBe('val'); 71 | }); 72 | 73 | it('Get length', () => { 74 | Vue.ls.set('item_one_test', 'val'); 75 | Vue.ls.set('item_two_test', 'val'); 76 | 77 | expect(Vue.ls.length).toBe(2); 78 | }); 79 | 80 | it('Serialized data', () => { 81 | Vue.ls.set('item_object', {foo: 'boo'}); 82 | Vue.ls.set('item_array', [2, 3, 4, 5]); 83 | Vue.ls.set('item_number', 6); 84 | Vue.ls.set('item_string', '7'); 85 | Vue.ls.set('item_boolean', false); 86 | 87 | expect(Vue.ls.get('item_object')).toEqual({foo: 'boo'}); 88 | expect(Vue.ls.get('item_array')).toEqual([2, 3, 4, 5]); 89 | expect(Vue.ls.get('item_number')).toEqual(6); 90 | expect(Vue.ls.get('item_string')).toEqual('7'); 91 | expect(Vue.ls.get('item_boolean')).toEqual(false); 92 | }); 93 | }); 94 | --------------------------------------------------------------------------------