Skip to content

Commit

Permalink
Merge pull request #144 from kristiankostadinov:search-result-tooltips
Browse files Browse the repository at this point in the history
Add tooltips to search result buttons
  • Loading branch information
martinhoefling authored Mar 13, 2020
2 parents ecd0a0d + 07a6638 commit c661de4
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 58 deletions.
14 changes: 3 additions & 11 deletions tests/unit/generic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,21 +123,13 @@ describe('localStorage wrappers', () => {
});

describe('createButtonWithCallback', () => {
test('callback works for button with style', () => {
test('creates button with callback and sets attributes', () => {
const buttonCbMock = jest.fn();
const button = generic.createButtonWithCallback('myclass', 'the text', 'border: 5px red;', buttonCbMock);
expect(buttonCbMock.mock.calls.length).toBe(0);
button.click();
expect(buttonCbMock.mock.calls.length).toBe(1);
expect(button.style._values.border).toEqual('5px red');
});

test('callback works for button without style', () => {
const buttonCbMock = jest.fn();
const button = generic.createButtonWithCallback('myclass', 'the text', null, buttonCbMock);
const button = generic.createButtonWithCallback({ className: 'myclass' }, buttonCbMock);
expect(buttonCbMock.mock.calls.length).toBe(0);
button.click();
expect(buttonCbMock.mock.calls.length).toBe(1);
expect(button.className).toEqual('myclass');
});
});

Expand Down
135 changes: 107 additions & 28 deletions tests/unit/search.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,28 +218,95 @@ describe('search method', () => {
return search.searchHost('mih').then(() => {
expect(global.setStatusText.mock.calls).toEqual([['__KEY_noResultsForMessage__ mih']]);
expect(global.createButtonWithCallback.mock.calls).toEqual([
['login', '__KEY_createNewEntryButtonText__', null, global.switchToCreateNewDialog],
[
{ className: 'login', textContent: '__KEY_createNewEntryButtonText__' },
global.switchToCreateNewDialog,
],
]);
});
});

test('creates entry with three additional buttons for non empty response', () => {
expect.assertions(3);
global.sendNativeAppMessage.mockResolvedValueOnce(['some/entry']);
return search.searchHost('mih').then(() => {
expect(global.createButtonWithCallback.mock.calls).toEqual([
['login', 'some/entry', "background-image: url('icons/si-glyph-key-2.svg')", search._onEntryAction],
['open', 'some/entry', null, expect.any(Function)],
['copy', 'some/entry', null, expect.any(Function)],
['details', 'some/entry', null, expect.any(Function)],
]);
describe('additional search result buttons', () => {
test('are created for non empty response', () => {
expect.assertions(2);
global.sendNativeAppMessage.mockResolvedValueOnce(['some/entry']);
return search.searchHost('mih').then(() => {
expect(global.createButtonWithCallback.mock.calls).toEqual([
[
{
className: 'login',
textContent: 'some/entry',
style: "background-image: url('icons/si-glyph-key-2.svg')",
title: '__KEY_searchResultLoginTooltip__',
},
search._onEntryAction,
],
[
{ className: 'open', textContent: 'some/entry', title: '__KEY_searchResultOpenTooltip__' },
expect.any(Function),
],
[
{ className: 'copy', textContent: 'some/entry', title: '__KEY_searchResultCopyTooltip__' },
expect.any(Function),
],
[
{
className: 'details',
textContent: 'some/entry',
title: '__KEY_searchResultDetailsTooltip__',
},
expect.any(Function),
],
]);

expect(global.sendNativeAppMessage.mock.calls).toEqual([[{ host: 'mih', type: 'queryHost' }]]);
global.sendNativeAppMessage.mockClear();
global.createButtonWithCallback.mock.calls[3][3]({
target: { innerText: 'text' },
expect(global.sendNativeAppMessage.mock.calls).toEqual([[{ host: 'mih', type: 'queryHost' }]]);
});
});

test("'Open' tries to open in a new tab", () => {
expect.assertions(1);
global.sendNativeAppMessage.mockResolvedValueOnce(['some/entry']);
return search.searchHost('mih').then(() => {
global.sendNativeAppMessage.mockClear();
global.createButtonWithCallback.mock.calls[1][1]({
target: { innerText: 'text' },
});
expect(global.browser.runtime.sendMessage.mock.calls).toEqual([
[{ entry: 'text', type: 'OPEN_TAB' }],
]);
});
});

test("'Copy' tries to copy to clipboard", () => {
expect.assertions(2);
global.sendNativeAppMessage.mockResolvedValueOnce(['some/entry']);
return search.searchHost('mih').then(() => {
global.sendNativeAppMessage.mockClear();
const messagePromise = Promise.resolve({ password: '1234' });
global.sendNativeAppMessage.mockImplementation(() => messagePromise);

global.createButtonWithCallback.mock.calls[2][1]({
target: { innerText: 'text' },
});

return messagePromise.then(() => {
expect(global.copyToClipboard.mock.calls).toEqual([['1234']]);
jest.runAllTimers();
expect(global.window.close.mock.calls.length).toBe(1);
});
});
});

test("'Details' tries to show details", () => {
expect.assertions(1);
global.sendNativeAppMessage.mockResolvedValueOnce(['some/entry']);
return search.searchHost('mih').then(() => {
global.sendNativeAppMessage.mockClear();
global.createButtonWithCallback.mock.calls[3][1]({
target: { innerText: 'text' },
});
expect(global.sendNativeAppMessage.mock.calls).toEqual([[{ entry: 'text', type: 'getData' }]]);
});
expect(global.sendNativeAppMessage.mock.calls).toEqual([[{ entry: 'text', type: 'getData' }]]);
});
});

Expand Down Expand Up @@ -269,9 +336,12 @@ describe('search method', () => {
global.sendNativeAppMessage.mockResolvedValueOnce(['some\\entry']);
return search.searchHost('entry').then(() => {
expect(global.createButtonWithCallback.mock.calls[0]).toEqual([
'login',
'some\\entry',
"background-image: url('icons/si-glyph-key-2.svg')",
{
className: 'login',
textContent: 'some\\entry',
style: "background-image: url('icons/si-glyph-key-2.svg')",
title: '__KEY_searchResultLoginTooltip__',
},
search._onEntryAction,
]);
});
Expand All @@ -284,9 +354,12 @@ describe('search method', () => {
global.sendNativeAppMessage.mockResolvedValueOnce(['some\\entry']);
return search.searchHost('entry').then(() => {
expect(global.createButtonWithCallback.mock.calls[0]).toEqual([
'login',
'some/entry',
"background-image: url('icons/si-glyph-key-2.svg')",
{
className: 'login',
textContent: 'some/entry',
style: "background-image: url('icons/si-glyph-key-2.svg')",
title: '__KEY_searchResultLoginTooltip__',
},
search._onEntryAction,
]);
});
Expand All @@ -303,9 +376,12 @@ describe('search method', () => {
global.sendNativeAppMessage.mockResolvedValueOnce(['some/entry']);
return search.searchHost('mih').then(() => {
expect(global.createButtonWithCallback.mock.calls[0]).toEqual([
'login',
'some/entry',
"background-image: url('http://some.host/fav.ico')",
{
className: 'login',
textContent: 'some/entry',
style: "background-image: url('http://some.host/fav.ico')",
title: '__KEY_searchResultLoginTooltip__',
},
search._onEntryAction,
]);
});
Expand All @@ -316,9 +392,12 @@ describe('search method', () => {
global.sendNativeAppMessage.mockResolvedValueOnce(['some/entry']);
return search.search('mih').then(() => {
expect(global.createButtonWithCallback.mock.calls[0]).toEqual([
'login',
'some/entry',
"background-image: url('icons/si-glyph-key-2.svg')",
{
className: 'login',
textContent: 'some/entry',
style: "background-image: url('icons/si-glyph-key-2.svg')",
title: '__KEY_searchResultLoginTooltip__',
},
search._onEntryAction,
]);
});
Expand Down
16 changes: 16 additions & 0 deletions web-extension/_locales/de/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@
"message": "Suchen",
"description": "caption of search button"
},
"searchResultLoginTooltip": {
"message": "Anmelden",
"description": "tooltip for login button in search result"
},
"searchResultOpenTooltip": {
"message": "In neuem Tab öffnen",
"description": "tooltip for open button in search result"
},
"searchResultCopyTooltip": {
"message": "Kopieren",
"description": "tooltip for copy button in search result"
},
"searchResultDetailsTooltip": {
"message": "Details anzeigen",
"description": "tooltip for details button in search result"
},
"createNameLabel": {
"message": "Name",
"description": "label for name"
Expand Down
16 changes: 16 additions & 0 deletions web-extension/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@
"message": "Search",
"description": "caption of search button"
},
"searchResultLoginTooltip": {
"message": "Login",
"description": "tooltip for login button in search result"
},
"searchResultOpenTooltip": {
"message": "Open in new tab",
"description": "tooltip for open button in search result"
},
"searchResultCopyTooltip": {
"message": "Copy",
"description": "tooltip for copy button in search result"
},
"searchResultDetailsTooltip": {
"message": "Show details",
"description": "tooltip for details button in search result"
},
"createNameLabel": {
"message": "Name",
"description": "label for name"
Expand Down
10 changes: 4 additions & 6 deletions web-extension/generic.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,11 @@ function executeOnSetting(setting, trueCallback, falseCallback) {
}, logError);
}

function createButtonWithCallback(className, text, style, callback) {
function createButtonWithCallback(attributes, callback) {
const element = document.createElement('button');
element.className = className;
element.textContent = text;
if (style) {
element.style = style;
}
Object.keys(attributes).forEach(attribute => {
element[attribute] = attributes[attribute];
});
element.addEventListener('click', callback);
return element;
}
Expand Down
52 changes: 39 additions & 13 deletions web-extension/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,34 +99,60 @@ function _faviconUrl() {
}

function _displaySearchResults(response, isHostQuery) {
const isWindows = window.navigator.userAgent.toLocaleLowerCase().includes('windows')
const results = document.getElementById('results');
results.innerHTML = '';
response.forEach(result => {
const isWindows = window.navigator.userAgent.toLocaleLowerCase().includes('windows');
const results = document.getElementById('results');
results.innerHTML = '';
response.forEach(result => {
// This is a workaround for gopass issue #1166 (windows only)
const item = isWindows ? result.replace(/\\/g, '/') : result
const item = isWindows ? result.replace(/\\/g, '/') : result;
const entry = document.createElement('div');
entry.classList.add('entry');

entry.appendChild(_createSearchResultLoginButton(item, isHostQuery));
entry.appendChild(
_createSimpleSearchResultButton('open', item, i18n.getMessage('searchResultOpenTooltip'), _onEntryOpen)
);
entry.appendChild(
_createSimpleSearchResultButton('copy', item, i18n.getMessage('searchResultCopyTooltip'), _onEntryCopy)
);
entry.appendChild(
createButtonWithCallback(
'login',
_createSimpleSearchResultButton(
'details',
item,
`background-image: url('${isHostQuery ? _faviconUrl() : 'icons/si-glyph-key-2.svg'}')`,
_onEntryAction
i18n.getMessage('searchResultDetailsTooltip'),
_onEntryDetails
)
);
entry.appendChild(createButtonWithCallback('open', item, null, event => _onEntryOpen(event.target)));
entry.appendChild(createButtonWithCallback('copy', item, null, event => _onEntryCopy(event.target)));
entry.appendChild(createButtonWithCallback('details', item, null, event => _onEntryDetails(event.target)));

results.appendChild(entry);
});
}

const _createSearchResultLoginButton = (item, isHostQuery) =>
createButtonWithCallback(
{
className: 'login',
textContent: item,
title: i18n.getMessage('searchResultLoginTooltip'),
style: `background-image: url('${isHostQuery ? _faviconUrl() : 'icons/si-glyph-key-2.svg'}')`,
},
_onEntryAction
);

const _createSimpleSearchResultButton = (className, text, title, clickHandler) =>
createButtonWithCallback({ className, title, textContent: text }, event => clickHandler(event.target));

function _displayNoResults() {
const results = document.getElementById('results');
setStatusText(i18n.getMessage('noResultsForMessage') + ' ' + searchTerm);
results.appendChild(
createButtonWithCallback('login', i18n.getMessage('createNewEntryButtonText'), null, switchToCreateNewDialog)
createButtonWithCallback(
{
className: 'login',
textContent: i18n.getMessage('createNewEntryButtonText'),
},
switchToCreateNewDialog
)
);
}

Expand Down

0 comments on commit c661de4

Please sign in to comment.