Commit a958e295 authored by tsergeant's avatar tsergeant Committed by Commit bot

MD Bookmarks: Integrate new data store with UI elements

MD Bookmarks is switching to a new data store layer to help with binding
data changes to UI elements. This CL ties the new data store to the
existing UI elements.

Note that the new data store is currently missing a few features:
* Item selection
* Search
* URL routing

These features are broken by this CL (there are no user-visible feature
regressions, since this is all behind a runtime flag), but will be added
back in soon.

BUG=697706
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.linux:closure_compilation

Review-Url: https://codereview.chromium.org/2735953002
Cr-Commit-Position: refs/heads/master@{#455981}
parent 32fbfd18
......@@ -257,6 +257,8 @@
<!-- MD Bookmarks. -->
<include name="IDR_MD_BOOKMARKS_ACTIONS_HTML" file="resources\md_bookmarks\actions.html" type="BINDATA" />
<include name="IDR_MD_BOOKMARKS_ACTIONS_JS" file="resources\md_bookmarks\actions.js" type="BINDATA" />
<include name="IDR_MD_BOOKMARKS_API_LISTENER_HTML" file="resources\md_bookmarks\api_listener.html" type="BINDATA" />
<include name="IDR_MD_BOOKMARKS_API_LISTENER_JS" file="resources\md_bookmarks\api_listener.js" type="BINDATA" />
<include name="IDR_MD_BOOKMARKS_APP_HTML" file="resources\md_bookmarks\app.html" type="BINDATA" />
<include name="IDR_MD_BOOKMARKS_APP_JS" file="resources\md_bookmarks\app.js" type="BINDATA" />
<include name="IDR_MD_BOOKMARKS_BOOKMARKS_HTML" file="resources\md_bookmarks\bookmarks.html" type="BINDATA" />
......
<link rel="import" href="chrome://resources/html/cr.html">
<link rel="import" href="chrome://bookmarks/actions.html">
<link rel="import" href="chrome://bookmarks/store.html">
<link rel="import" href="chrome://bookmarks/util.html">
<script src="chrome://bookmarks/api_listener.js"></script>
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Listener functions which translate events from the
* chrome.bookmarks API into actions to modify the local page state.
*/
cr.define('bookmarks.ApiListener', function() {
/** @param {Action} action */
function dispatch(action) {
bookmarks.Store.getInstance().handleAction(action);
}
/**
* @param {string} id
* @param {{title: string, url: (string|undefined)}} changeInfo
*/
function onBookmarkChanged(id, changeInfo) {
dispatch(bookmarks.actions.editBookmark(id, changeInfo));
}
/**
* @param {string} id
* @param {{parentId: string, index: number}} removeInfo
*/
function onBookmarkRemoved(id, removeInfo) {
dispatch(bookmarks.actions.removeBookmark(
id, removeInfo.parentId, removeInfo.index));
}
function onImportBegan() {
// TODO(rongjie): pause onCreated once this event is used.
}
function onImportEnded() {
chrome.bookmarks.getTree(function(results) {
dispatch(bookmarks.actions.refreshNodes(
bookmarks.util.normalizeNodes(results[0])));
});
}
function init() {
chrome.bookmarks.onChanged.addListener(onBookmarkChanged);
chrome.bookmarks.onRemoved.addListener(onBookmarkRemoved);
chrome.bookmarks.onImportBegan.addListener(onImportBegan);
chrome.bookmarks.onImportEnded.addListener(onImportEnded);
}
return {
init: init,
};
});
<link rel="import" href="chrome://resources/html/polymer.html">
<link rel="import" href="chrome://bookmarks/api_listener.html">
<link rel="import" href="chrome://bookmarks/list.html">
<link rel="import" href="chrome://bookmarks/shared_vars.html">
<link rel="import" href="chrome://bookmarks/sidebar.html">
<link rel="import" href="chrome://bookmarks/store.html">
<link rel="import" href="chrome://bookmarks/toolbar.html">
<link rel="import" href="chrome://bookmarks/util.html">
<dom-module id="bookmarks-app">
<template>
......@@ -30,18 +32,11 @@
flex: 1;
}
</style>
<bookmarks-toolbar search-term="[[searchTerm]]"></bookmarks-toolbar>
<bookmarks-toolbar></bookmarks-toolbar>
<div id="main-container">
<bookmarks-sidebar root-folders="[[rootNode.children]]">
</bookmarks-sidebar>
<bookmarks-list displayed-list="[[displayedList]]"
search-term="[[searchTerm]]"></bookmarks-list>
<bookmarks-sidebar></bookmarks-sidebar>
<bookmarks-list></bookmarks-list>
</div>
<bookmarks-store selected-id="{{selectedId}}"
root-node="{{rootNode}}"
search-term="{{searchTerm}}"
displayed-list="{{displayedList}}">
</bookmarks-store>
</template>
<script src="chrome://bookmarks/app.js"></script>
</dom-module>
......@@ -5,21 +5,20 @@
Polymer({
is: 'bookmarks-app',
properties: {
selectedId: String,
/** @type {BookmarkTreeNode} */
rootNode: Object,
searchTerm: String,
/** @type {Array<BookmarkTreeNode>} */
displayedList: Array,
},
behaviors: [
bookmarks.StoreClient,
],
/** @override */
attached: function() {
/** @type {BookmarksStore} */ (this.$$('bookmarks-store'))
.initializeStore();
chrome.bookmarks.getTree(function(results) {
var nodeList = bookmarks.util.normalizeNodes(results[0]);
var initialState = bookmarks.util.createEmptyState();
initialState.nodes = nodeList;
initialState.selectedFolder = nodeList['0'].children[0];
bookmarks.Store.getInstance().init(initialState);
bookmarks.ApiListener.init();
}.bind(this));
},
});
......@@ -13,11 +13,23 @@
],
'includes': ['../../../../third_party/closure_compiler/compile_js2.gypi']
},
{
'target_name': 'api_listener',
'dependencies': [
'<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr',
'<(EXTERNS_GYP):chrome_extensions',
'actions',
'bookmarks_store',
'util',
],
'includes': ['../../../../third_party/closure_compiler/compile_js2.gypi'],
},
{
'target_name': 'app',
'dependencies': [
'<(EXTERNS_GYP):chrome_extensions',
'store',
'api_listener',
'store_client',
],
'includes': ['../../../../third_party/closure_compiler/compile_js2.gypi'],
},
......@@ -36,7 +48,8 @@
'includes': ['../../../../third_party/closure_compiler/compile_js2.gypi'],
'dependencies': [
'<(EXTERNS_GYP):chrome_extensions',
'store',
'actions',
'store_client',
],
},
{
......@@ -44,6 +57,8 @@
'dependencies': [
'<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:icon',
'<(EXTERNS_GYP):chrome_extensions',
'actions',
'store_client',
],
'includes': ['../../../../third_party/closure_compiler/compile_js2.gypi'],
},
......@@ -54,7 +69,9 @@
'<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data',
'<(EXTERNS_GYP):bookmark_manager_private',
'<(EXTERNS_GYP):chrome_extensions',
'actions',
'item',
'store_client',
],
'includes': ['../../../../third_party/closure_compiler/compile_js2.gypi'],
},
......@@ -74,7 +91,7 @@
{
'target_name': 'sidebar',
'dependencies': [
'folder_node',
'store_client',
],
'includes': ['../../../../third_party/closure_compiler/compile_js2.gypi'],
},
......
......@@ -3,8 +3,10 @@
<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html">
<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
<link rel="import" href="chrome://bookmarks/actions.html">
<link rel="import" href="chrome://bookmarks/icons.html">
<link rel="import" href="chrome://bookmarks/shared_style.html">
<link rel="import" href="chrome://bookmarks/store_client.html">
<dom-module id="bookmarks-folder-node">
<template>
......@@ -43,8 +45,8 @@
overflow: hidden;
}
:host([is-selected-folder]) .menu-label,
:host([is-selected-folder]) #folder-label {
:host([is-selected-folder_]) .menu-label,
:host([is-selected-folder_]) #folder-label {
color: var(--folder-active-color);
}
......@@ -65,19 +67,20 @@
<div id="container" class="v-centered">
<div id="folder-label" class="v-centered" on-tap="selectFolder_">
<iron-icon icon="[[getFolderIcon_(isSelectedFolder)]]"></iron-icon>
<div class="menu-label">[[item.title]]</div>
<iron-icon icon="[[getFolderIcon_(isSelectedFolder_)]]"></iron-icon>
<div class="menu-label">[[item_.title]]</div>
</div>
<template is="dom-if" if="[[hasChildFolder_(item.children)]]">
<paper-icon-button icon="[[getArrowIcon_(item.isOpen)]]"
<template is="dom-if" if="[[hasChildFolder_(item_.children)]]">
<paper-icon-button icon="[[getArrowIcon_(isClosed_)]]"
on-tap="toggleFolder_"></paper-icon-button>
</template>
</div>
<div id="descendants" hidden$="[[!item.isOpen]]">
<template is="dom-repeat" items="[[item.children]]"
filter="isFolder_" observe="url" as="child">
<bookmarks-folder-node item="[[child]]"
is-selected-folder="[[child.isSelectedFolder]]">
<div id="descendants" hidden$="[[isClosed_]]">
<template is="dom-repeat"
items="[[item_.children]]"
as="child"
filter="isFolder_">
<bookmarks-folder-node item-id="[[child]]">
</bookmarks-folder-node>
</template>
</div>
......
......@@ -5,23 +5,54 @@
Polymer({
is: 'bookmarks-folder-node',
behaviors: [
bookmarks.StoreClient,
],
properties: {
/** @type {BookmarkTreeNode} */
item: Object,
itemId: {
type: String,
observer: 'updateFromStore',
},
/** @type {BookmarkNode} */
item_: Object,
/** @private */
isClosed_: Boolean,
isSelectedFolder: {
/** @private */
selectedFolder_: String,
/** @private */
isSelectedFolder_: {
type: Boolean,
value: false,
reflectToAttribute: true,
computed: 'computeIsSelected_(itemId, selectedFolder_)'
},
},
attached: function() {
this.watch('item_', function(state) {
return state.nodes[this.itemId];
}.bind(this));
this.watch('isClosed_', function(state) {
return !!state.closedFolders[this.itemId];
}.bind(this));
this.watch('selectedFolder_', function(state) {
return state.selectedFolder;
});
this.updateFromStore();
},
/**
* @private
* @return {string}
*/
getFolderIcon_: function() {
return this.isSelectedFolder ? 'bookmarks:folder-open' : 'cr:folder';
return this.isSelectedFolder_ ? 'bookmarks:folder-open' : 'cr:folder';
},
/**
......@@ -29,12 +60,12 @@ Polymer({
* @return {string}
*/
getArrowIcon_: function() {
return this.item.isOpen ? 'cr:arrow-drop-up' : 'cr:arrow-drop-down';
return this.isClosed_ ? 'cr:arrow-drop-down' : 'cr:arrow-drop-up';
},
/** @private */
selectFolder_: function() {
this.fire('selected-folder-changed', this.item.id);
this.dispatch(bookmarks.actions.selectFolder(this.item_.id));
},
/**
......@@ -42,10 +73,18 @@ Polymer({
* @private
*/
toggleFolder_: function() {
this.fire('folder-open-changed', {
id: this.item.id,
open: !this.item.isOpen,
});
this.dispatch(
bookmarks.actions.changeFolderOpen(this.item_.id, this.isClosed_));
},
/**
* @param {string} itemId
* @param {string} selectedFolder
* @return {boolean}
* @private
*/
computeIsSelected_: function(itemId, selectedFolder) {
return itemId == selectedFolder;
},
/**
......@@ -53,19 +92,19 @@ Polymer({
* @return {boolean}
*/
hasChildFolder_: function() {
for (var i = 0; i < this.item.children.length; i++) {
if (!this.item.children[i].url)
for (var i = 0; i < this.item_.children.length; i++) {
if (this.isFolder_(this.item_.children[i]))
return true;
}
return false;
},
/**
* @param {BookmarkTreeNode} item
* @param {string} itemId
* @private
* @return {boolean}
*/
isFolder_: function(item) {
return !item.url;
isFolder_: function(itemId) {
return !this.getState().nodes[itemId].url;
}
});
......@@ -3,7 +3,9 @@
<link rel="import" href="chrome://resources/html/icon.html">
<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html">
<link rel="import" href="chrome://bookmarks/actions.html">
<link rel="import" href="chrome://bookmarks/shared_style.html">
<link rel="import" href="chrome://bookmarks/store_client.html">
<dom-module id="bookmarks-item">
<template>
......@@ -17,7 +19,7 @@
height: 40px;
}
:host([is-selected-item]) {
:host([is-selected-item_]) {
background-color: rgb(225, 235, 253);
}
......@@ -60,7 +62,7 @@
<div id="icon" hidden$="[[isFolder_]]"></div>
</div>
<div id="website-title">
[[item.title]]
[[item_.title]]
</div>
<button is="paper-icon-button-light" class="more-vert-button"
on-click="onMenuButtonOpenClick_">
......
......@@ -5,23 +5,34 @@
Polymer({
is: 'bookmarks-item',
behaviors: [
bookmarks.StoreClient,
],
properties: {
/** @type {BookmarkTreeNode} */
item: {
itemId: {
type: String,
observer: 'updateFromStore',
},
/** @private {BookmarkNode} */
item_: {
type: Object,
observer: 'onItemChanged_',
},
isFolder_: Boolean,
isSelectedItem: {
/** @private */
isSelectedItem_: {
type: Boolean,
reflectToAttribute: true,
},
/** @private */
isFolder_: Boolean,
},
observers: [
'updateFavicon_(item.url)',
'updateFavicon_(item_.url)',
],
listeners: {
......@@ -29,6 +40,14 @@ Polymer({
'dblclick': 'onDblClick_',
},
attached: function() {
this.watch('item_', function(store) {
return store.nodes[this.itemId];
}.bind(this));
this.updateFromStore();
},
/**
* @param {Event} e
* @private
......@@ -37,13 +56,13 @@ Polymer({
e.stopPropagation();
this.fire('open-item-menu', {
target: e.target,
item: this.item,
item: this.item_,
});
},
/** @private */
onItemChanged_: function() {
this.isFolder_ = !(this.item.url);
this.isFolder_ = !(this.item_.url);
},
/**
......@@ -52,7 +71,7 @@ Polymer({
*/
onClick_: function(e) {
this.fire('select-item', {
item: this.item,
item: this.item_,
range: e.shiftKey,
add: e.ctrlKey,
});
......@@ -63,10 +82,10 @@ Polymer({
* @private
*/
onDblClick_: function(e) {
if (!this.item.url)
this.fire('selected-folder-changed', this.item.id);
if (!this.item_.url)
this.dispatch(bookmarks.actions.selectFolder(this.item_.id));
else
chrome.tabs.create({url: this.item.url});
chrome.tabs.create({url: this.item_.url});
},
/**
......
......@@ -8,6 +8,8 @@
<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/shadow.html">
<link rel="import" href="chrome://bookmarks/item.html">
<link rel="import" href="chrome://bookmarks/shared_style.html">
<link rel="import" href="chrome://bookmarks/store_client.html">
<link rel="import" href="chrome://bookmarks/util.html">
<dom-module id="bookmarks-list">
<template>
......@@ -71,9 +73,8 @@
</div>
</dialog>
<div id="bookmarksCard" hidden$="[[isEmptyList_(displayedList.length)]]">
<template is="dom-repeat" items="[[displayedList]]" as="item">
<bookmarks-item item="[[item]]"
is-selected-item="[[item.isSelectedItem]]">
<template is="dom-repeat" items="[[displayedList]]" as="id">
<bookmarks-item item-id="[[id]]">
</bookmarks-item>
</template>
</div>
......
......@@ -5,12 +5,23 @@
Polymer({
is: 'bookmarks-list',
behaviors: [
bookmarks.StoreClient,
],
properties: {
/** @type {BookmarkTreeNode} */
/** @type {BookmarkNode} */
menuItem_: Object,
/** @type {Array<BookmarkTreeNode>} */
displayedList: Array,
/** @type {Array<string>} */
displayedList: {
type: Array,
value: function() {
// Use an empty list during initialization so that the databinding to
// hide #bookmarksCard takes effect.
return [];
},
},
searchTerm: String,
},
......@@ -19,6 +30,13 @@ Polymer({
'open-item-menu': 'onOpenItemMenu_',
},
attached: function() {
this.watch('displayedList', function(state) {
return bookmarks.util.getDisplayedList(state);
});
this.updateFromStore();
},
/**
* @param {Event} e
* @private
......
<link rel="import" href="chrome://resources/html/cr.html">
<!-- TODO(tsergeant): Remove this import once actions are used elsewhere. -->
<link rel="import" href="chrome://bookmarks/actions.html">
<script src="chrome://bookmarks/reducers.js"></script>
......@@ -2,6 +2,7 @@
<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
<link rel="import" href="chrome://resources/cr_elements/icons.html">
<link rel="import" href="chrome://bookmarks/folder_node.html">
<link rel="import" href="chrome://bookmarks/store_client.html">
<dom-module id="bookmarks-sidebar">
<template>
......@@ -18,8 +19,7 @@
<div id="folder-tree">
<template is="dom-repeat" items="[[rootFolders]]">
<bookmarks-folder-node item="[[item]]"
is-selected-folder="[[item.isSelectedFolder]]">
<bookmarks-folder-node item-id="[[item]]">
</bookmarks-folder-node>
</template>
</div>
......
......@@ -5,7 +5,19 @@
Polymer({
is: 'bookmarks-sidebar',
behaviors: [
bookmarks.StoreClient,
],
properties: {
/** @type {Array<string>} */
rootFolders: Array,
},
attached: function() {
this.watch('rootFolders', function(store) {
return store.nodes['0'].children;
});
this.updateFromStore();
},
});
......@@ -40,6 +40,10 @@ cr.define('bookmarks', function() {
* Note that object identity is used to determine if the value has changed
* before updating the UI, rather than Polymer-style deep equality.
*
* Typechecking is supressed because this conflicts with
* Object.prototype.watch, which is a Gecko-only method that is recognized
* by Closure.
* @suppress {checkTypes}
* @param {string} localProperty
* @param {function(!BookmarksPageState)} valueGetter
*/
......@@ -87,5 +91,7 @@ cr.define('bookmarks', function() {
},
};
return {StoreClient: StoreClient};
return {
StoreClient: StoreClient,
};
});
......@@ -68,6 +68,9 @@ content::WebUIDataSource* CreateMdBookmarksUIHTMLSource(Profile* profile) {
// Resources.
source->AddResourcePath("actions.html", IDR_MD_BOOKMARKS_ACTIONS_HTML);
source->AddResourcePath("actions.js", IDR_MD_BOOKMARKS_ACTIONS_JS);
source->AddResourcePath("api_listener.html",
IDR_MD_BOOKMARKS_API_LISTENER_HTML);
source->AddResourcePath("api_listener.js", IDR_MD_BOOKMARKS_API_LISTENER_JS);
source->AddResourcePath("app.html", IDR_MD_BOOKMARKS_APP_HTML);
source->AddResourcePath("app.js", IDR_MD_BOOKMARKS_APP_JS);
source->AddResourcePath("bookmarks_store.js",
......
......@@ -4,51 +4,36 @@
suite('<bookmarks-item>', function() {
var item;
var store;
var TEST_ITEM = createItem('0');
setup(function() {
store = new bookmarks.TestStore({
nodes: testTree(createFolder('1', [createItem(['0'])])),
});
bookmarks.Store.instance_ = store;
item = document.createElement('bookmarks-item');
item.itemId = '0';
replaceBody(item);
item.item = TEST_ITEM;
});
test('changing the url changes the favicon', function() {
var favicon = item.$.icon.style.backgroundImage;
item.set('item.url', 'http://www.mail.google.com');
store.data.nodes['0'] = createItem('0', {url: 'https://mail.google.com'});
store.notifyObservers();
assertNotEquals(favicon, item.$.icon.style.backgroundImage);
});
test('changing isFolder_ hides/unhides the folder/icon', function() {
item.isFolder_ = true;
assertFalse(item.$['folder-icon'].hidden);
assertTrue(item.$.icon.hidden);
item.isFolder_ = false;
test('changing to folder hides/unhides the folder/icon', function() {
// Starts test as an item.
assertTrue(item.$['folder-icon'].hidden);
assertFalse(item.$.icon.hidden);
});
test('pressing the buttons fires the right event', function() {
var counter = [0, 0, 0];
document.addEventListener('select-item', function(e) {
if (e.detail.range)
counter[0]++;
else if (e.detail.add)
counter[1]++;
else
counter[2]++;
});
customClick(item);
assertDeepEquals([0, 0, 1], counter);
customClick(item, {shiftKey: true});
assertDeepEquals([1, 0, 1], counter);
// Change to a folder.
item.itemId = '1';
customClick(item, {ctrlKey: true});
assertDeepEquals([1, 1, 1], counter);
customClick(item, {shiftKey: true, ctrlKey: true});
assertDeepEquals([2, 1, 1], counter);
assertFalse(item.$['folder-icon'].hidden);
assertTrue(item.$.icon.hidden);
});
});
......@@ -4,28 +4,40 @@
suite('<bookmarks-list>', function() {
var list;
var TEST_LIST =
[createItem('0'), createItem('1'), createFolder('2', [], null)];
var store;
setup(function() {
store = new bookmarks.TestStore({
nodes: testTree(createFolder(
'0',
[
createItem('1'),
createFolder('3', []),
createItem('5'),
createItem('7'),
])),
selectedFolder: '0',
});
bookmarks.Store.instance_ = store;
list = document.createElement('bookmarks-list');
replaceBody(list);
list.displayedList = TEST_LIST;
Polymer.dom.flush();
});
test('folder menu item hides the url field', function() {
// Bookmark editor shows the url field.
list.menuItem_ = TEST_LIST[0];
list.menuItem_ = store.data.nodes['1'];
assertFalse(list.$['url'].hidden);
// Folder editor hides the url field.
list.menuItem_ = TEST_LIST[2];
list.menuItem_ = store.data.nodes['3'];
assertTrue(list.$['url'].hidden);
});
test('saving edit passes correct details to the update', function() {
// Saving bookmark edit.
var menuItem = TEST_LIST[0];
var menuItem = store.data.nodes['1'];
chrome.bookmarks.update = function(id, edit) {
assertEquals(menuItem.id, id);
assertEquals(menuItem.url, edit.url);
......@@ -36,7 +48,7 @@ suite('<bookmarks-list>', function() {
MockInteractions.tap(list.$.saveButton);
// Saving folder rename.
menuItem = TEST_LIST[2];
menuItem = store.data.nodes['3'];
chrome.bookmarks.update = function(id, edit) {
assertEquals(menuItem.id, id);
assertEquals(menuItem.title, edit.title);
......@@ -46,4 +58,11 @@ suite('<bookmarks-list>', function() {
list.$.editBookmark.showModal();
MockInteractions.tap(list.$.saveButton);
});
test('renders correct <bookmark-item> elements', function() {
var items = list.root.querySelectorAll('bookmarks-item');
var ids = Array.from(items).map((item) => item.itemId);
assertDeepEquals(['1', '3', '5', '7'], ids);
});
});
......@@ -22,6 +22,7 @@ MaterialBookmarksBrowserTest.prototype = {
switchValue: 'MaterialDesignBookmarks'}],
extraLibraries: PolymerTest.getLibraries(ROOT_PATH).concat([
'test_store.js',
'test_util.js',
]),
};
......
......@@ -4,39 +4,33 @@
suite('<bookmarks-sidebar>', function() {
var sidebar;
var TEST_TREE;
var store;
setup(function() {
TEST_TREE = createFolder('0', [
createFolder(
'1',
[
createFolder(
'2',
[
createFolder('3', []),
createFolder('4', []),
]),
createItem('5'),
createItem('6'),
]),
createFolder('7', []),
createFolder('8', []),
]);
store = new bookmarks.TestStore({
nodes: testTree(createFolder('0', [
createFolder(
'1',
[
createFolder(
'2',
[
createFolder('3', []),
createFolder('4', []),
]),
createItem('5'),
]),
createFolder('7', []),
])),
});
bookmarks.Store.instance_ = store;
setupTreeForUITests(TEST_TREE);
sidebar = document.createElement('bookmarks-sidebar');
replaceBody(sidebar);
sidebar.rootFolders = TEST_TREE.children;
Polymer.dom.flush();
});
test('selecting and deselecting folders fires event', function() {
var firedId;
document.addEventListener('selected-folder-changed', function(e) {
firedId = /** @type {string} */ (e.detail);
});
Polymer.dom.flush();
test('selecting and deselecting folders dispatches action', function() {
var rootFolders = sidebar.$['folder-tree'].children;
var firstGen = rootFolders[0].$['descendants'].querySelectorAll(
'bookmarks-folder-node');
......@@ -46,11 +40,13 @@ suite('<bookmarks-sidebar>', function() {
// Select nested folder.
firedId = '';
MockInteractions.tap(secondGen[0].$['folder-label']);
assertEquals(secondGen[0].item.id, firedId);
assertEquals('select-folder', store.lastAction.name);
assertEquals(secondGen[0].itemId, store.lastAction.id);
// Select folder in a separate subtree.
firedId = '';
MockInteractions.tap(rootFolders[1].$['folder-label']);
assertEquals(rootFolders[1].item.id, firedId);
assertEquals('select-folder', store.lastAction.name);
assertEquals(rootFolders[1].itemId, store.lastAction.id);
});
});
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('bookmarks', function() {
var TestStore = function(data) {
this.data = Object.assign(bookmarks.util.createEmptyState(), data);
this.lastAction_ = null;
this.observers_ = [];
};
TestStore.prototype = {
addObserver: function(client) {
this.observers_.push(client);
},
removeObserver: function(client) {},
isInitialized: function() {
return true;
},
handleAction: function(action) {
this.lastAction_ = action;
},
get lastAction() {
return this.lastAction_;
},
notifyObservers: function() {
// TODO(tsergeant): Revisit how state modifications work in UI tests.
// We don't want tests to worry about modifying the whole state tree.
// Instead, we could perform a deep clone in here to ensure that every
// StoreClient is updated.
this.observers_.forEach((client) => client.onStateChanged(this.data));
},
};
return {
TestStore: TestStore,
};
});
......@@ -18,20 +18,6 @@ function testTree(root) {
return bookmarks.util.normalizeNodes(root);
}
/**
* Initialize a tree for UI testing. This performs the same initialization as
* `setUpStore_` in <bookmarks-store>, but without the need for a store element
* in the test.
* @param {BookmarkTreeNode} rootNode
*/
function setupTreeForUITests(rootNode){
if (!rootNode.path)
rootNode.path = 'rootNode';
BookmarksStore.generatePaths(rootNode, 0);
BookmarksStore.initNodes(rootNode);
}
/**
* Creates a folder with given properties.
* @param {string} id
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment