Merge branch 'master' into develop

# Conflicts:
#	release-notes.md
pull/1348/head
antelle 2019-10-27 11:05:23 +01:00
commit 604c1bd3d4
11 changed files with 400 additions and 17 deletions

View File

@ -333,14 +333,6 @@ module.exports = function(grunt) {
}
}
},
codesign: {
dmg: {
options: {
identity: 'app'
},
src: [`dist/desktop/KeeWeb-${pkg.version}.mac.dmg`]
}
},
compress: {
options: {
level: 6

View File

@ -15,7 +15,7 @@ function emitSet(target, value, prevValue) {
emitter.emit('add', value, target);
updates.added.push(value);
}
emitter.emit('change', updates, this);
emitter.emit('change', updates, target);
}
}
@ -25,7 +25,7 @@ function emitRemoved(target, removed) {
for (const item of removed) {
emitter.emit('remove', item, target);
}
emitter.emit('change', { added: [], removed }, this);
emitter.emit('change', { added: [], removed }, target);
}
}
@ -127,6 +127,7 @@ class Collection {
this[SymbolEvents].emit('remove', item, this);
this[SymbolEvents].emit('change', { added: [], removed: [item] }, this);
}
return item;
}
shift() {
@ -137,6 +138,7 @@ class Collection {
this[SymbolEvents].emit('remove', item, this);
this[SymbolEvents].emit('change', { added: [], removed: [item] }, this);
}
return item;
}
unshift(...items) {
@ -197,7 +199,7 @@ class Collection {
}
sort() {
this[SymbolArray].sort(this.comparator);
return this[SymbolArray].sort(this.comparator);
}
fill() {

View File

@ -53,7 +53,15 @@ class SettingsShortcutsView extends View {
shortcutClick(e) {
const globalShortcutType = e.target.dataset.shortcut;
const shortcutEditor = $('<div/>').addClass('shortcut__editor');
const existing = $(`.shortcut__editor[data-shortcut=${globalShortcutType}]`);
if (existing.length) {
existing.remove();
return;
}
const shortcutEditor = $('<div/>')
.addClass('shortcut__editor')
.attr('data-shortcut', globalShortcutType);
$('<div/>')
.text(Locale.setShEdit)
.appendTo(shortcutEditor);

View File

@ -1,6 +1,6 @@
{
"name": "KeeWeb",
"version": "1.12.0",
"version": "1.12.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "KeeWeb",
"version": "1.12.0",
"version": "1.12.1",
"description": "Free cross-platform password manager compatible with KeePass",
"main": "main.js",
"homepage": "https://keeweb.info",

View File

@ -47,7 +47,7 @@ module.exports = function(grunt) {
'compress:linux-x64'
]);
grunt.registerTask('build-desktop-dist-darwin', ['appdmg', 'codesign:dmg']);
grunt.registerTask('build-desktop-dist-darwin', ['appdmg']);
grunt.registerTask('build-desktop-dist-win32', [
'nsis:win32-un-x64',

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "keeweb",
"version": "1.12.0",
"version": "1.12.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "keeweb",
"version": "1.12.0",
"version": "1.12.1",
"description": "Free cross-platform password manager compatible with KeePass",
"main": "Gruntfile.js",
"private": true,

View File

@ -3,6 +3,10 @@ Release notes
##### v1.13.0 (TBD)
`-` fix #1323: version in the About dialog
##### v1.12.1 (2019-10-27)
`-` fix #1324: duplicated shortcut editor in settings
`-` fix #1313: disabled code signing for macOS dmg
##### v1.12.0 (2019-10-26)
`-` #1022: fuzzy search
`+` #1108: setting for running in an iframe

View File

@ -0,0 +1,373 @@
import { expect } from 'chai';
import { Model } from 'framework/model';
import { Collection } from 'framework/collection';
describe('Collection', () => {
class TestModel extends Model {
constructor(id) {
super();
this.id = id;
}
}
TestModel.defineModelProperties({ id: 'bar' });
class TestCollection extends Collection {
static model = TestModel;
}
it('should create a collection without models', () => {
const collection = new TestCollection();
expect(collection).to.be.ok;
expect(collection.length).to.eql(0);
});
it('should create a collection with models', () => {
const collection = new TestCollection([new TestModel('1'), new TestModel('2')]);
expect(collection).to.be.ok;
expect(collection.length).to.eql(2);
expect(collection[0]).to.be.ok;
expect(collection[0].id).to.eql('1');
expect(collection[1]).to.be.ok;
expect(collection[1].id).to.eql('2');
expect(collection[2]).to.be.undefined;
});
it('should check types when adding new items', () => {
const collection = new TestCollection();
expect(() => {
collection.push({ id: 'bar' });
}).to.throw();
});
it('should serialize a collection to JSON', () => {
const collection = new TestCollection([new TestModel('1'), new TestModel('2')]);
expect(JSON.stringify(collection)).to.eql('[{"id":"1"},{"id":"2"}]');
});
it('should add models with push', () => {
const collection = new TestCollection();
const callsChange = [];
const callsAdd = [];
collection.on('change', (...args) => callsChange.push(args));
collection.on('add', (...args) => callsAdd.push(args));
expect(JSON.stringify(collection)).to.eql('[]');
const model1 = new TestModel(1);
collection.push(model1);
expect(callsChange).to.eql([[{ added: [model1], removed: [] }, collection]]);
expect(callsAdd).to.eql([[model1, collection]]);
expect(collection.length).to.eql(1);
expect(JSON.stringify(collection)).to.eql('[{"id":1}]');
callsChange.length = 0;
callsAdd.length = 0;
const modelA = new TestModel('a');
const modelB = new TestModel('b');
collection.push(modelA, modelB);
expect(callsChange).to.eql([[{ added: [modelA, modelB], removed: [] }, collection]]);
expect(callsAdd).to.eql([[modelA, collection], [modelB, collection]]);
expect(collection.length).to.eql(3);
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":"a"},{"id":"b"}]');
});
it('should add models with unshift', () => {
const collection = new TestCollection([new TestModel(1)]);
const callsChange = [];
const callsAdd = [];
collection.on('change', (...args) => callsChange.push(args));
collection.on('add', (...args) => callsAdd.push(args));
expect(JSON.stringify(collection)).to.eql('[{"id":1}]');
const model2 = new TestModel(2);
collection.unshift(model2);
expect(callsChange).to.eql([[{ added: [model2], removed: [] }, collection]]);
expect(callsAdd).to.eql([[model2, collection]]);
expect(collection.length).to.eql(2);
expect(JSON.stringify(collection)).to.eql('[{"id":2},{"id":1}]');
callsChange.length = 0;
callsAdd.length = 0;
const modelA = new TestModel('a');
const modelB = new TestModel('b');
collection.unshift(modelA, modelB);
expect(callsChange).to.eql([[{ added: [modelA, modelB], removed: [] }, collection]]);
expect(callsAdd).to.eql([[modelA, collection], [modelB, collection]]);
expect(collection.length).to.eql(4);
expect(JSON.stringify(collection)).to.eql('[{"id":"a"},{"id":"b"},{"id":2},{"id":1}]');
});
it('should remove models with pop', () => {
const model1 = new TestModel(1);
const model2 = new TestModel(2);
const collection = new TestCollection([model1, model2]);
const callsChange = [];
const callsRemove = [];
collection.on('change', (...args) => callsChange.push(args));
collection.on('remove', (...args) => callsRemove.push(args));
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":2}]');
const popped1 = collection.pop();
expect(popped1).to.eql(model2);
expect(callsChange).to.eql([[{ added: [], removed: [model2] }, collection]]);
expect(callsRemove).to.eql([[model2, collection]]);
expect(collection.length).to.eql(1);
expect(JSON.stringify(collection)).to.eql('[{"id":1}]');
callsChange.length = 0;
callsRemove.length = 0;
const popped2 = collection.pop();
expect(popped2).to.eql(model1);
expect(callsChange).to.eql([[{ added: [], removed: [model1] }, collection]]);
expect(callsRemove).to.eql([[model1, collection]]);
expect(JSON.stringify(collection)).to.eql('[]');
callsChange.length = 0;
callsRemove.length = 0;
const popped3 = collection.pop();
expect(popped3).to.be.undefined;
expect(callsChange).to.eql([]);
expect(callsRemove).to.eql([]);
expect(JSON.stringify(collection)).to.eql('[]');
});
it('should remove models with shift', () => {
const model1 = new TestModel(1);
const model2 = new TestModel(2);
const collection = new TestCollection([model1, model2]);
const callsChange = [];
const callsRemove = [];
collection.on('change', (...args) => callsChange.push(args));
collection.on('remove', (...args) => callsRemove.push(args));
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":2}]');
const shifted1 = collection.shift();
expect(shifted1).to.eql(model1);
expect(callsChange).to.eql([[{ added: [], removed: [model1] }, collection]]);
expect(callsRemove).to.eql([[model1, collection]]);
expect(collection.length).to.eql(1);
expect(JSON.stringify(collection)).to.eql('[{"id":2}]');
callsChange.length = 0;
callsRemove.length = 0;
const shifted2 = collection.shift();
expect(shifted2).to.eql(model2);
expect(callsChange).to.eql([[{ added: [], removed: [model2] }, collection]]);
expect(callsRemove).to.eql([[model2, collection]]);
expect(collection.length).to.eql(0);
expect(JSON.stringify(collection)).to.eql('[]');
callsChange.length = 0;
callsRemove.length = 0;
const shifted3 = collection.shift();
expect(shifted3).to.be.undefined;
expect(callsChange).to.eql([]);
expect(callsRemove).to.eql([]);
expect(collection.length).to.eql(0);
expect(JSON.stringify(collection)).to.eql('[]');
});
it('should remove models by setting length', () => {
const model1 = new TestModel(1);
const model2 = new TestModel(2);
const model3 = new TestModel(3);
const collection = new TestCollection([model1, model2, model3]);
const callsChange = [];
const callsRemove = [];
collection.on('change', (...args) => callsChange.push(args));
collection.on('remove', (...args) => callsRemove.push(args));
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":2},{"id":3}]');
collection.length = 1;
expect(callsChange).to.eql([[{ added: [], removed: [model2, model3] }, collection]]);
expect(callsRemove).to.eql([[model2, collection], [model3, collection]]);
expect(collection.length).to.eql(1);
expect(JSON.stringify(collection)).to.eql('[{"id":1}]');
callsChange.length = 0;
callsRemove.length = 0;
collection.length = 1;
expect(callsChange).to.eql([]);
expect(callsRemove).to.eql([]);
expect(collection.length).to.eql(1);
expect(JSON.stringify(collection)).to.eql('[{"id":1}]');
collection.length = 0;
expect(callsChange).to.eql([[{ added: [], removed: [model1] }, collection]]);
expect(callsRemove).to.eql([[model1, collection]]);
expect(collection.length).to.eql(0);
expect(JSON.stringify(collection)).to.eql('[]');
});
it('should set custom properties', () => {
const collection = new TestCollection();
const calls = [];
collection.on('change', (...args) => calls.push(args));
collection.on('add', (...args) => calls.push(args));
collection.on('remove', (...args) => calls.push(args));
collection.prop = 'val';
expect(collection.prop).to.eql('val');
expect(collection.length).to.eql(0);
expect(JSON.stringify(collection)).to.eql('[]');
expect(calls).to.eql([]);
});
it('should check types when setting items', () => {
const collection = new TestCollection();
expect(() => {
collection[0] = { id: 'bar' };
}).to.throw();
});
it('should set new items', () => {
const collection = new TestCollection();
const callsChange = [];
const callsAdd = [];
const callsRemove = [];
collection.on('change', (...args) => callsChange.push(args));
collection.on('add', (...args) => callsAdd.push(args));
collection.on('remove', (...args) => callsRemove.push(args));
const model = new TestModel(1);
collection[0] = model;
expect(collection.length).to.eql(1);
expect(JSON.stringify(collection)).to.eql('[{"id":1}]');
expect(callsChange).to.eql([[{ added: [model], removed: [] }, collection]]);
expect(callsAdd).to.eql([[model, collection]]);
expect(callsRemove).to.eql([]);
});
it('should set existing items', () => {
const model1 = new TestModel(1);
const model2 = new TestModel(2);
const collection = new TestCollection([model1, model2]);
const callsChange = [];
const callsAdd = [];
const callsRemove = [];
collection.on('change', (...args) => callsChange.push(args));
collection.on('add', (...args) => callsAdd.push(args));
collection.on('remove', (...args) => callsRemove.push(args));
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":2}]');
const model3 = new TestModel(3);
collection[1] = model3;
expect(collection.length).to.eql(2);
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":3}]');
expect(callsChange).to.eql([[{ added: [model3], removed: [model2] }, collection]]);
expect(callsAdd).to.eql([[model3, collection]]);
expect(callsRemove).to.eql([[model2, collection]]);
});
it('should get items by id', () => {
const model1 = new TestModel(1);
const model2 = new TestModel(2);
const collection = new TestCollection([model1, model2]);
expect(collection.get(1)).to.eql(model1);
expect(collection.get(2)).to.eql(model2);
expect(collection.get(3)).to.be.undefined;
});
it('should remove items', () => {
const model1 = new TestModel(1);
const model2 = new TestModel(2);
const collection = new TestCollection([model1, model2]);
const callsChange = [];
const callsRemove = [];
collection.on('change', (...args) => callsChange.push(args));
collection.on('remove', (...args) => callsRemove.push(args));
collection.remove(1);
expect(JSON.stringify(collection)).to.eql('[{"id":2}]');
expect(callsChange).to.eql([[{ added: [], removed: [model1] }, collection]]);
expect(callsRemove).to.eql([[model1, collection]]);
callsChange.length = 0;
callsRemove.length = 0;
collection.remove(model2);
expect(JSON.stringify(collection)).to.eql('[]');
expect(callsChange).to.eql([[{ added: [], removed: [model2] }, collection]]);
expect(callsRemove).to.eql([[model2, collection]]);
});
it('should sort items by comparator', () => {
const model1 = new TestModel(1);
const model2 = new TestModel(2);
const collection = new TestCollection([model1, model2]);
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":2}]');
collection.comparator = (x, y) => x.id - y.id;
collection.sort();
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":2}]');
collection.comparator = (x, y) => y.id - x.id;
collection.sort();
expect(JSON.stringify(collection)).to.eql('[{"id":2},{"id":1}]');
});
it('should splice items', () => {
const model1 = new TestModel(1);
const model2 = new TestModel(2);
const model3 = new TestModel(3);
const collection = new TestCollection([model1, model2, model3]);
const callsChange = [];
const callsAdd = [];
const callsRemove = [];
collection.on('change', (...args) => callsChange.push(args));
collection.on('add', (...args) => callsAdd.push(args));
collection.on('remove', (...args) => callsRemove.push(args));
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":2},{"id":3}]');
const model4 = new TestModel(4);
const model5 = new TestModel(5);
const model6 = new TestModel(6);
collection.splice(1, 1, model4, model5, model6);
expect(collection.length).to.eql(5);
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":4},{"id":5},{"id":6},{"id":3}]');
expect(callsChange).to.eql([
[{ added: [model4, model5, model6], removed: [model2] }, collection]
]);
expect(callsAdd).to.eql([[model4, collection], [model5, collection], [model6, collection]]);
expect(callsRemove).to.eql([[model2, collection]]);
});
});

View File

@ -11,6 +11,7 @@ const rootDir = path.join(__dirname, '..');
module.exports = {
mode: 'development',
devtool: 'source-map',
entry: path.resolve(__dirname, 'index.js'),
output: {
path: path.resolve(__dirname, 'dist'),
@ -22,5 +23,8 @@ module.exports = {
...appConfig.resolve.alias,
test: path.resolve(rootDir, 'test')
}
},
module: {
rules: appConfig.module.rules
}
};