Browse Source

Fix save options and add end to end tests

pull/342/head
Guillaume Vincent 7 years ago
parent
commit
3e673965ec
12 changed files with 144 additions and 114 deletions
  1. +7
    -7
      dist/lesspass.min.js
  2. +1
    -1
      src/components/Menu.vue
  3. +36
    -25
      src/components/Options.vue
  4. +0
    -6
      src/store/actions.js
  5. +12
    -0
      src/store/getters.js
  6. +2
    -1
      src/store/index.js
  7. +0
    -2
      src/store/mutation-types.js
  8. +0
    -13
      src/store/mutations.js
  9. +10
    -10
      src/views/PasswordGenerator.vue
  10. +20
    -0
      test/e2e/specs/defaultOptions.js
  11. +56
    -0
      test/unit/store.getters.js
  12. +0
    -49
      test/unit/store.mutations.js

+ 7
- 7
dist/lesspass.min.js
File diff suppressed because it is too large
View File


+ 1
- 1
src/components/Menu.vue View File

@@ -18,7 +18,7 @@
<div class="card-header" v-bind:class="{ 'card-inverse': isGuest}">
<div class="row">
<div class="col-3">
<span v-on:click="fullReload()" class="white-link pointer">LessPass</span>
<span id="title" v-on:click="fullReload()" class="white-link pointer">LessPass</span>
</div>
<div class="col-9 text-right">
<span v-if="saved && isAuthenticated">


+ 36
- 25
src/components/Options.vue View File

@@ -22,32 +22,32 @@
<div class="col-3">
<button id="lowercase__btn"
type="button" class="btn btn-block btn-sm px-0"
v-bind:class="{'btn-primary':password.lowercase===true && password.version===2,'btn-warning':password.lowercase===true && password.version===1,'btn-secondary':password.lowercase===false}"
v-on:click="password.lowercase=!password.lowercase">
v-bind:class="{'btn-primary':options.lowercase===true && options.version===2,'btn-warning':options.lowercase===true && options.version===1,'btn-secondary':options.lowercase===false}"
v-on:click="options.lowercase=!options.lowercase">
a-z
</button>
</div>
<div class="col-3">
<button id="uppercase__btn"
type="button" class="btn btn-block btn-sm px-0"
v-bind:class="{'btn-primary':password.uppercase===true && password.version===2,'btn-warning':password.uppercase===true && password.version===1,'btn-secondary':password.uppercase===false}"
v-on:click="password.uppercase=!password.uppercase">
v-bind:class="{'btn-primary':options.uppercase===true && options.version===2,'btn-warning':options.uppercase===true && options.version===1,'btn-secondary':options.uppercase===false}"
v-on:click="options.uppercase=!options.uppercase">
A-Z
</button>
</div>
<div class="col-3">
<button id="numbers__btn"
type="button" class="btn btn-block btn-sm px-0"
v-bind:class="{'btn-primary':password.numbers===true && password.version===2,'btn-warning':password.numbers===true && password.version===1,'btn-secondary':password.numbers===false}"
v-on:click="password.numbers=!password.numbers">
v-bind:class="{'btn-primary':options.numbers===true && options.version===2,'btn-warning':options.numbers===true && options.version===1,'btn-secondary':options.numbers===false}"
v-on:click="options.numbers=!options.numbers">
0-9
</button>
</div>
<div class="col-3">
<button id="symbols__btn"
type="button" class="btn btn-block btn-sm px-0"
v-bind:class="{'btn-primary':password.symbols===true && password.version===2,'btn-warning':password.symbols===true && password.version===1,'btn-secondary':password.symbols===false}"
v-on:click="password.symbols=!password.symbols">
v-bind:class="{'btn-primary':options.symbols===true && options.version===2,'btn-warning':options.symbols===true && options.version===1,'btn-secondary':options.symbols===false}"
v-on:click="options.symbols=!options.symbols">
%!@
</button>
</div>
@@ -58,7 +58,7 @@
<div class="col">
<label for="passwordLength">{{ $t('Length') }}</label>
<div class="input-group input-group-sm">
<span class="input-group-btn" v-on:click="password.length=decrement(password.length, {min: 5, max: 35})">
<span class="input-group-btn" v-on:click="options.length=decrement(options.length, {min: 5, max: 35})">
<button id="decreaseLength__btn" class="btn btn-secondary p-1" type="button">
<i class="fa fa-minus"></i>
</button>
@@ -68,9 +68,9 @@
type="number"
min="5"
max="35"
v-model.number="password.length">
v-model.number="options.length">
<span class="input-group-btn"
v-on:click="password.length=increment(password.length, {min: 5, max: 35})">
v-on:click="options.length=increment(options.length, {min: 5, max: 35})">
<button id="increaseLength__btn" class="btn btn-secondary p-1" type="button">
<i class="fa fa-plus"></i>
</button>
@@ -80,11 +80,12 @@
<div class="col">
<label for="passwordCounter"
class="hint--top hint--medium"
v-bind:aria-label="$t('CounterFieldHelp','Increment this value to change the generated password without changing your master password.')">
v-bind:aria-label="$t('CounterFieldHelp','Increment this value to change the generated password without changing your master options.')">
{{$t('Counter')}}
</label>
<div class="input-group input-group-sm">
<span id="decreaseCounter__btn" class="input-group-btn" v-on:click="password.counter=decrement(password.counter, {min: 1})">
<span id="decreaseCounter__btn" class="input-group-btn"
v-on:click="options.counter=decrement(options.counter, {min: 1})">
<button class="btn btn-secondary p-1" type="button">
<i class="fa fa-minus"></i>
</button>
@@ -93,8 +94,9 @@
class="form-control form-control-sm"
type="number"
min="1"
v-model.number="password.counter">
<span id="increaseCounter__btn" class="input-group-btn" v-on:click="password.counter=increment(password.counter, {min: 1})">
v-model.number="options.counter">
<span id="increaseCounter__btn" class="input-group-btn"
v-on:click="options.counter=increment(options.counter, {min: 1})">
<button class="btn btn-secondary p-1" type="button">
<i class="fa fa-plus"></i>
</button>
@@ -110,7 +112,7 @@
<div class="row no-gutters">
<div class="col">
<button type="button" class="btn btn-block btn-sm border-right-0"
v-bind:class="{'btn-primary':password.version===2,'btn-secondary':password.version!==2}"
v-bind:class="{'btn-primary':options.version===2,'btn-secondary':options.version!==2}"
v-on:click="setVersion(2)">
<span class="hidden-xs-up">{{$t('version')}} </span>
<span class="hidden-xs-down">{{$t('versionShortcut', 'v')}}</span>2
@@ -119,7 +121,7 @@
<div class="col">
<button type="button"
class="btn btn-block btn-sm border-left-0"
v-bind:class="{'btn-warning':password.version===1,'btn-secondary':password.version!==1}"
v-bind:class="{'btn-warning':options.version===1,'btn-secondary':options.version!==1}"
v-on:click="setVersion(1)">
<span class="hidden-xs-up">{{$t('version')}} </span>
<span class="hidden-xs-down">{{$t('versionShortcut', 'v')}}</span>1
@@ -130,9 +132,10 @@
</div>
<div class="form-group row mb-0">
<div class="col">
<button type="button" class="btn btn-sm hint--top-right hint--medium"
<button id="saveOptions__btn"
type="button" class="btn btn-sm hint--top-right hint--medium"
v-bind:aria-label="$t('DefaultOptionLocalStorage', 'We use local storage to save default options locally. Each time you open the app, these options will be loaded by default.')"
v-bind:class="{'btn-outline-warning':password.version===1,'btn-outline-primary':password.version!==1}"
v-bind:class="{'btn-outline-warning':options.version===1,'btn-outline-primary':options.version!==1}"
v-on:click="saveDefaultOptions()">
<i class="fa fa-floppy-o"></i> {{$t('Save options')}}
</button>
@@ -142,31 +145,39 @@
</template>

<script type="text/ecmascript-6">
import {mapState} from 'vuex';
import message from '../services/message';
import {increment, decrement} from "../services/form-validator";

export default {
name: 'options',
computed: mapState(['password']),
props: {
options: Object
},
watch: {
'options': {
handler: function(options) {
this.$emit('update:options', options)
},
deep: true
}
},
methods: {
decrement,
increment,
setVersion(value){
setVersion(value) {
if (value === 1) {
message.error(this.$t(
"WarningV1Deprecated",
"Version 1 is deprecated and will be deleted soon. We strongly advise you to migrate your passwords to version 2."
));
}
const password = Object.assign({}, this.password, {
this.options = Object.assign({}, this.options, {
length: value === 1 ? 12 : 16,
version: value
});
this.$store.dispatch('savePassword', {password});
this.$store.dispatch('saveVersion', {version: value});
},
saveDefaultOptions(){
saveDefaultOptions() {
this.$store.dispatch('saveDefaultOptions', {options: this.options});
message.success(this.$t('Your options have been saved successfully'));
},


+ 0
- 6
src/store/actions.js View File

@@ -25,7 +25,6 @@ export const loadBestPasswordProfile = ({ commit }) => {
if (site) {
commit(types.SET_SITE, { site });
commit(types.LOAD_PASSWORD_PROFILE, { site });
commit(types.CHECK_SHOW_OPTIONS);
}
});
};
@@ -39,7 +38,6 @@ export const getPasswordFromUrlQuery = ({ commit }, { query }) => {

export const savePassword = ({ commit }, payload) => {
commit(types.SET_PASSWORD, payload);
commit(types.CHECK_SHOW_OPTIONS);
};

export const saveVersion = ({ commit }, payload) => {
@@ -64,10 +62,6 @@ export const logout = ({ commit }) => {
commit(types.LOGOUT);
};

export const toggleShowOptions = ({ commit }) => {
commit(types.TOGGLE_SHOW_OPTIONS);
};

export const getPasswords = ({ commit, state }) => {
if (state.authenticated) {
Password.all(state)


+ 12
- 0
src/store/getters.js View File

@@ -1,3 +1,5 @@
import { defaultOptions } from "./defaultPassword";

export const isAuthenticated = state => state.authenticated;

export const isGuest = state => !state.authenticated;
@@ -16,3 +18,13 @@ export const passwordURL = state => {
.password.symbols}&length=${state.password.length}&counter=${state.password
.counter}&version=${state.password.version}`;
};

export const isDefaultProfile = state => {
let defaultProfile = true;
for (let key in defaultOptions) {
if (defaultOptions[key] !== state.password[key]) {
defaultProfile = false;
}
}
return defaultProfile;
};

+ 2
- 1
src/store/index.js View File

@@ -24,5 +24,6 @@ export default new Vuex.Store({
getters,
actions,
mutations,
plugins: [createPersistedState({ key: "lesspass" })]
plugins: [createPersistedState({ key: "lesspass" })],
strict: true
});

+ 0
- 2
src/store/mutation-types.js View File

@@ -12,5 +12,3 @@ export const SET_SITE = "SET_SITE";
export const LOAD_PASSWORD_PROFILE = "LOAD_PASSWORD_PROFILE";
export const DELETE_PASSWORD = "DELETE_PASSWORD";
export const CLEAN_MESSAGE = "CLEAN_MESSAGE";
export const CHECK_SHOW_OPTIONS = "CHECK_SHOW_OPTIONS";
export const TOGGLE_SHOW_OPTIONS = "TOGGLE_SHOW_OPTIONS";

+ 0
- 13
src/store/mutations.js View File

@@ -1,4 +1,3 @@
import { defaultOptions } from "./defaultPassword";
import * as types from "./mutation-types";

export default {
@@ -64,17 +63,5 @@ export default {
},
[types.CLEAN_MESSAGE](state) {
state.message = { text: "", status: "success" };
},
[types.CHECK_SHOW_OPTIONS](state) {
let showOptions = false;
for (let key in defaultOptions) {
if (defaultOptions[key] !== state.password[key]) {
showOptions = true;
}
}
state.showOptions = showOptions;
},
[types.TOGGLE_SHOW_OPTIONS](state) {
state.showOptions = !state.showOptions;
}
};

+ 10
- 10
src/views/PasswordGenerator.vue View File

@@ -61,7 +61,7 @@
<button type="button"
class="btn btn-secondary pull-right showOptions__btn"
v-show="!passwordGenerated"
v-on:click="toggleShowOptions()">
v-on:click="showOptions =! showOptions">
<i class="fa fa-sliders"></i>
</button>
</div>
@@ -101,20 +101,20 @@
<span class="input-group-btn">
<button type="button"
class="btn btn-secondary showOptions__btn"
v-on:click="toggleShowOptions()">
v-on:click="showOptions =! showOptions">
<i class="fa fa-sliders"></i>
</button>
</span>
</div>
</div>
</div>
<options v-if="showOptions"></options>
<options :options.sync="password" v-if="showOptions || !isDefaultProfile"></options>
</form>
</template>

<script type="text/ecmascript-6">
import LessPass from 'lesspass';
import {mapState} from 'vuex';
import {mapGetters, mapState} from 'vuex';
import copy from 'copy-text-to-clipboard';
import RemoveAutoComplete from '../components/RemoveAutoComplete.vue';
import MasterPassword from '../components/MasterPassword.vue';
@@ -131,7 +131,8 @@
Options
},
computed: {
...mapState(['passwords', 'password', 'passwordURL', 'showOptions']),
...mapState(['passwords']),
...mapGetters(['passwordURL', 'isDefaultProfile'])
},
beforeMount() {
this.$store.dispatch('getPasswords');
@@ -145,6 +146,8 @@
},
data() {
return {
password: {...this.$store.state.password},
showOptions: false,
masterPassword: '',
fingerprint: '',
passwordGenerated: '',
@@ -183,9 +186,6 @@
}
},
methods: {
toggleShowOptions() {
this.$store.dispatch('toggleShowOptions');
},
togglePasswordType(element) {
if (element.type === 'password') {
element.type = 'text';
@@ -235,8 +235,8 @@
focusBestInputField() {
const site = this.$refs.site;
const login = this.$refs.login;
const masterPassword = this.$refs.masterPassword.$refs.passwordField;
site.value ? (login.value ? masterPassword.focus() : login.focus()) : site.focus();
const masterPassword = this.$refs.masterPassword;
site.value ? (login.value ? masterPassword.$refs.passwordField.focus() : login.focus()) : site.focus();
},
copyPassword() {
const copied = copy(this.passwordGenerated);


+ 20
- 0
test/e2e/specs/defaultOptions.js View File

@@ -0,0 +1,20 @@
var assert = require("assert");

module.exports = {
"Set default options": function(browser) {
browser
.url(browser.launch_url)
.waitForElementVisible("#site")
.setValue("#site", "lesspass.com")
.setValue("#login", "test@lesspass.com")
.click(".showOptions__btn")
.waitForElementVisible("#saveOptions__btn")
.click("#saveOptions__btn")
.click("#title")
.refresh()
.assert.value("#site", "lesspass.com")
.assert.value("#login", "test@lesspass.com");

browser.end();
}
};

+ 56
- 0
test/unit/store.getters.js View File

@@ -41,6 +41,62 @@ test("passwordURL", t => {
);
});

test("isDefaultProfile", t => {
const state = {
password: {
login: "test@example.org",
site: "example.org",
uppercase: true,
lowercase: true,
numbers: true,
symbols: true,
length: 16,
counter: 1,
version: 2
},
defaultPassword: {
login: "",
site: "",
uppercase: true,
lowercase: true,
numbers: true,
symbols: true,
length: 16,
counter: 1,
version: 2
}
};
t.true(getters.isDefaultProfile(state));
});

test("isDefaultProfile false", t => {
const state = {
password: {
login: "test@example.org",
site: "example.org",
uppercase: true,
lowercase: true,
numbers: true,
symbols: false,
length: 32,
counter: 1,
version: 1
},
defaultPassword: {
login: "",
site: "",
uppercase: true,
lowercase: true,
numbers: true,
symbols: true,
length: 16,
counter: 1,
version: 2
}
};
t.false(getters.isDefaultProfile(state));
});

test("isAuthenticated", t => {
const state = {
authenticated: true


+ 0
- 49
test/unit/store.mutations.js View File

@@ -438,52 +438,3 @@ test("CLEAN_MESSAGE", t => {
t.is(state.message.text, "");
t.is(state.message.status, "success");
});

test("CHECK_SHOW_OPTIONS false", t => {
const state = {
password: {
login: "test@example.org",
site: "example.org",
uppercase: true,
lowercase: true,
numbers: true,
symbols: true,
length: 16,
counter: 1,
version: 2
}
};
const CHECK_SHOW_OPTIONS = mutations[types.CHECK_SHOW_OPTIONS];
CHECK_SHOW_OPTIONS(state);
t.false(state.showOptions);
});

test("CHECK_SHOW_OPTIONS true", t => {
const state = {
password: {
login: "test@example.org",
site: "example.org",
uppercase: true,
lowercase: true,
numbers: true,
symbols: false,
length: 32,
counter: 1,
version: 1
}
};
const CHECK_SHOW_OPTIONS = mutations[types.CHECK_SHOW_OPTIONS];
CHECK_SHOW_OPTIONS(state);
t.true(state.showOptions);
});

test("TOGGLE_SHOW_OPTIONS", t => {
const TOGGLE_SHOW_OPTIONS = mutations[types.TOGGLE_SHOW_OPTIONS];
const state = {
showOptions: false
};
TOGGLE_SHOW_OPTIONS(state);
t.true(state.showOptions);
TOGGLE_SHOW_OPTIONS(state);
t.false(state.showOptions);
});

Loading…
Cancel
Save