@neovici/cosmoz-treenode-navigator 3.0.8 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/helpers.js ADDED
@@ -0,0 +1,252 @@
1
+ import { DefaultTree } from '@neovici/cosmoz-tree/cosmoz-default-tree';
2
+ import basicTree from './test/data/basicTree';
3
+ import { tagged as css } from '@neovici/cosmoz-utils/lib/tagged';
4
+
5
+ const tree = new DefaultTree(basicTree),
6
+ /**
7
+ * Returns true if a given node has children
8
+ * @param {Object} node - The node
9
+ * @return {Boolean} - True if node has children
10
+ */
11
+ hasChildren = node => {
12
+ return tree.hasChildren(node);
13
+ },
14
+ /**
15
+ * Returns true, if a search string is eligable to trigger a search
16
+ * @param {String} value - The search string
17
+ * @param {Number} searchMinLength - The minimum length of value to be valid
18
+ * @return {Boolean} - If a search should be triggered
19
+ */
20
+ computeSearching = (value, searchMinLength) => {
21
+ return value && value.length >= searchMinLength && value !== '';
22
+ },
23
+ /**
24
+ * Normalizes and returns an Array of nodes
25
+ * with the properties name, path, parentSectionName, children
26
+ * @param {Array} nodes - The input nodes
27
+ * @return {Array} - The normalized nodes
28
+ */
29
+ normalizeNodes = nodes => {
30
+ if (!Array.isArray(nodes)) {
31
+ return [];
32
+ }
33
+ return nodes.map(node => {
34
+ if (!node) {
35
+ return node;
36
+ }
37
+ const path = node.pathLocator || node.path,
38
+ pathLocatorSeparator = tree.pathLocatorSeparator,
39
+ parentPath = path.includes(pathLocatorSeparator) ? path.substring(0, path.lastIndexOf(pathLocatorSeparator)) : path;
40
+ return {
41
+ name: node[tree.searchProperty],
42
+ path,
43
+ children: node[tree.childProperty],
44
+ parentSectionName: tree.getPathString(parentPath, tree.searchProperty),
45
+ id: node.id,
46
+ pathLocator: node.pathLocator
47
+ };
48
+ });
49
+ },
50
+ /**
51
+ * Returns the found nodes based on a search string and a given tree to be searched
52
+ * @param {Boolean} searching - If true, a search should be executed
53
+ * @param {String} searchString - The search string
54
+ * @param {Array} renderedLevel - The node list on which the search should be executed
55
+ * @param {Tree} tree - The main tree
56
+ * @return {Array} - The found nodes
57
+ */
58
+ computeDataPlane = (searching, searchString, renderedLevel, tree) => {
59
+ if (searching && tree) {
60
+ const results = tree.searchNodes(searchString, renderedLevel, false);
61
+ return normalizeNodes(results);
62
+ }
63
+ return renderedLevel;
64
+ },
65
+ /**
66
+ * Returns a node array with the children of a node on the given path
67
+ * If the node doesn't have children, the node gets returned
68
+ * @param {String} pathLocator - The separated address parts of a node
69
+ * @param {Tree} tree - The main tree
70
+ * @return {Array} - Nodes
71
+ */
72
+ renderLevel = (pathLocator, tree) => {
73
+ if (!tree) {
74
+ return;
75
+ }
76
+ const node = tree.getNodeByPathLocator(pathLocator),
77
+ children = tree.getChildren(node),
78
+ level = tree.hasChildren(node) ? children : node,
79
+ sortFunc = (a, b) => {
80
+ // First sort based on "folder" status (containing children)
81
+ if (hasChildren(a)) {
82
+ if (!hasChildren(b)) {
83
+ return -1;
84
+ }
85
+ } else if (hasChildren(b)) {
86
+ return 1;
87
+ }
88
+ // Then sort on searchProperty
89
+ const val1 = a[tree.searchProperty],
90
+ val2 = b[tree.searchProperty];
91
+
92
+ if (val1 > val2) {
93
+ return 1;
94
+ }
95
+ if (val1 < val2) {
96
+ return -1;
97
+ }
98
+ return 0;
99
+ };
100
+ return normalizeNodes(level)
101
+ .sort(sortFunc);
102
+ },
103
+ /**
104
+ * Returns the classes of a row based its selection state
105
+ * @param {String} classes - The default classes
106
+ * @param {object} node - Node to check
107
+ * @param {object} highlightedNode - Currently highlighted node
108
+ * @return {String} - The CSS classes
109
+ */
110
+ computeRowClass = (classes, node, highlightedNode) => {
111
+ let selected = false;
112
+ if (highlightedNode) {
113
+ selected = node === highlightedNode
114
+ || node.id && node.id === highlightedNode.id
115
+ || node.pathLocator === highlightedNode.pathLocator;
116
+ }
117
+ return selected ? classes + ' selected' : classes;
118
+ },
119
+ /**
120
+ * Selects the doubled clicked node and dispatches an node-dblclicked event.
121
+ * @param {Event} event The triggering event
122
+ * @param {object} host Host
123
+ * @return {undefined}
124
+ */
125
+ onNodeDblClicked = (event, host) => {
126
+ host.dispatchEvent(new CustomEvent('node-dblclicked', {
127
+ detail: {
128
+ model: event.model
129
+ }
130
+ }));
131
+ },
132
+ /**
133
+ * Returns the name of a given node
134
+ * @param {Object} node - The node
135
+ * @return {String} - The name
136
+ */
137
+ getNodeName = node => {
138
+ return node[tree.searchProperty];
139
+ },
140
+ /**
141
+ * Returns true, if the button should be visible
142
+ * @param {Boolean} searching - If a search is currently executed
143
+ * @param {String} openNodeLevelPath - The open node level
144
+ * @return {Boolean} - The visibility of the button
145
+ */
146
+ showGlobalSearchBtn = (searching, openNodeLevelPath) => {
147
+ return searching && openNodeLevelPath !== '';
148
+ },
149
+ /**
150
+ * Returns the nodes on a path specified by a given path locator
151
+ * @param {String} pathLocator - The separated address parts of a node
152
+ * @param {Tree} tree - The main tree
153
+ * @return {Array} - The found nodes or empty array
154
+ */
155
+ getTreePathParts = (pathLocator, tree) => {
156
+ if (!tree || !pathLocator) {
157
+ return [];
158
+ }
159
+ return normalizeNodes(tree.getPathNodes(pathLocator));
160
+ },
161
+ /**
162
+ * Returns a node based on a given path locator.
163
+ * If pathLocator is empty or not defined, null gets returned.
164
+ * If pathLocator is only partly valid, the last valid node gets returned.
165
+ * @param {String} pathLocator - The separated address parts of a node
166
+ * @param {Tree} tree - The main tree
167
+ * @return {Object} - The found node
168
+ */
169
+ getNode = (pathLocator, tree) => {
170
+ if (!tree || !pathLocator) {
171
+ return null;
172
+ }
173
+
174
+ const node = tree.getNodeByPathLocator(pathLocator);
175
+ let nodes;
176
+
177
+ if (!node) {
178
+ nodes = tree.getPathNodes(pathLocator).filter(n => n != null);
179
+ }
180
+ return nodes && nodes.length > 0 ? nodes.pop() : node;
181
+ },
182
+ nodeStyles = css`
183
+ .section {
184
+ background-color: #f5f5f5;
185
+ padding: 5px;
186
+ }
187
+
188
+ .pointer {
189
+ cursor: pointer;
190
+ }
191
+
192
+ .item {
193
+ padding: 0;
194
+ }
195
+
196
+ .item[data-index] {
197
+ background: transparent;
198
+ }
199
+
200
+ .node-item {
201
+ align-items: center;
202
+ display: flex;
203
+ font-family: 'Roboto', 'Noto', sans-serif;
204
+ font-size: 16px;
205
+ font-weight: 400;
206
+ height: 40px;
207
+ line-height: 24px;
208
+ padding: 6px 12px;
209
+ }
210
+
211
+ .node-item-wrapper {
212
+ width: 100%;
213
+ }
214
+
215
+ .icon {
216
+ box-sizing: border-box;
217
+ cursor: pointer;
218
+ display: inline-block;
219
+ height: 40px;
220
+ line-height: 1;
221
+ outline: none;
222
+ padding: 8px;
223
+ position: relative;
224
+ user-select: none;
225
+ width: 40px;
226
+ z-index: 0;
227
+ }
228
+
229
+ .node-item.selected {
230
+ background-color: var(--cosmoz-listbox-active-color, var(--cosmoz-selection-color, rgba(58, 145, 226, 0.1)));
231
+ transition: background-color 0.2s ease-out;
232
+ }
233
+
234
+ .node-item.selected .icon svg {
235
+ fill: var(--cosmoz-treenode-navigator-select-node-icon-color);
236
+ transition: color 0.8s ease-out;
237
+ }`;
238
+
239
+ export {
240
+ hasChildren,
241
+ computeSearching,
242
+ normalizeNodes,
243
+ renderLevel,
244
+ computeDataPlane,
245
+ computeRowClass,
246
+ onNodeDblClicked,
247
+ getNodeName,
248
+ showGlobalSearchBtn,
249
+ getTreePathParts,
250
+ getNode,
251
+ nodeStyles
252
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neovici/cosmoz-treenode-navigator",
3
- "version": "3.0.8",
3
+ "version": "4.0.0",
4
4
  "description": "A Polymer component that lets you navigate and search through hierarchically structured data-nodes and select one of them.",
5
5
  "keywords": [
6
6
  "polymer",
@@ -44,27 +44,41 @@
44
44
  "commitlint": {
45
45
  "extends": [
46
46
  "@commitlint/config-conventional"
47
- ]
47
+ ],
48
+ "rules": {
49
+ "body-max-line-length": [
50
+ 1,
51
+ "always",
52
+ 100
53
+ ]
54
+ }
48
55
  },
49
56
  "dependencies": {
57
+ "@neovici/cfg": "^1.15.2",
58
+ "@neovici/cosmoz-autocomplete": "^4.0.0",
50
59
  "@neovici/cosmoz-dialog": "^3.0.0",
51
60
  "@neovici/cosmoz-i18next": "^3.2.0",
61
+ "@neovici/cosmoz-input": "^2.0.0",
52
62
  "@neovici/cosmoz-tree": "^3.0.0",
63
+ "@neovici/cosmoz-utils": "^4.0.0",
64
+ "@open-wc/lit-helpers": "^0.4.1",
53
65
  "@polymer/iron-icons": "^3.0.0",
54
- "@polymer/iron-list": "^3.1.0",
55
66
  "@polymer/paper-button": "^3.0.0",
56
67
  "@polymer/paper-icon-button": "^3.0.0",
57
68
  "@polymer/paper-input": "^3.2.0",
58
69
  "@polymer/polymer": "^3.4.0",
59
70
  "@web/dev-server": "^0.1.25",
60
71
  "@web/test-runner": "^0.13.19",
61
- "@web/test-runner-selenium": "^0.5.2"
72
+ "@web/test-runner-selenium": "^0.5.2",
73
+ "haunted": "^5.0.0",
74
+ "lit": "^2.2.7",
75
+ "lit-html": "^2.0.0"
62
76
  },
63
77
  "devDependencies": {
64
- "@commitlint/cli": "^13.0.0",
65
- "@commitlint/config-conventional": "^13.0.0",
78
+ "@commitlint/cli": "^17.0.0",
79
+ "@commitlint/config-conventional": "^17.0.0",
66
80
  "@neovici/eslint-config": "^1.3.0",
67
- "@open-wc/testing": "^2.5.33",
81
+ "@open-wc/testing": "^3.0.0",
68
82
  "@polymer/iron-ajax": "^3.0.0",
69
83
  "@polymer/iron-component-page": "^4.0.0",
70
84
  "@polymer/iron-demo-helpers": "^3.0.0",
@@ -75,6 +89,6 @@
75
89
  "@webcomponents/webcomponentsjs": "^2.0.0",
76
90
  "eslint": "^7.0.0",
77
91
  "husky": "^6.0.0",
78
- "semantic-release": "^18.0.0"
92
+ "semantic-release": "^19.0.3"
79
93
  }
80
94
  }
@@ -1,56 +1,85 @@
1
1
  import { assert, fixture, html } from '@open-wc/testing';
2
2
  import { DefaultTree } from '@neovici/cosmoz-tree/cosmoz-default-tree';
3
3
  import basicTree from './data/basicTree.js';
4
- import { flush } from '@polymer/polymer/lib/utils/flush';
5
4
  import '../cosmoz-treenode-navigator';
5
+ import { computeDataPlane, computeSearching, hasChildren, renderLevel } from '../helpers';
6
6
 
7
7
  suite('cosmoz-treenode-navigator', () => {
8
8
  let navigator;
9
9
 
10
10
  setup(async () => {
11
- navigator = await fixture(html`<cosmoz-treenode-navigator .tree=${ new DefaultTree(basicTree) }>`);
12
- flush();
11
+ navigator = await fixture(html`
12
+ <cosmoz-treenode-navigator
13
+ .tree=${ new DefaultTree(basicTree) }
14
+ .searchPlaceholder="${ 'Custom search placeholder' }"
15
+ .searchMinLength="${ 2 }"
16
+ >`);
13
17
  });
14
18
 
15
19
  test('instantiating the element', () => {
16
20
  assert.equal(navigator.tagName, 'COSMOZ-TREENODE-NAVIGATOR');
17
21
  });
18
22
 
19
- test('dataPlane initialised with tree', () => {
20
- assert.equal(navigator.dataPlane[0].name, 'Root');
21
- assert.equal(
22
- navigator.dataPlane.length,
23
- navigator.tree._roots.length
24
- );
25
- });
26
-
27
23
  test('tree length and sort', () => {
28
24
  // given a node navigator with some data
29
25
 
30
26
  // when I enter the first node
31
- navigator.shadowRoot
32
- .querySelector('#ironList paper-icon-button')
33
- .click();
34
- flush();
27
+ navigator.openNodePath = '1000';
35
28
 
29
+ const dataPlane = computeDataPlane(
30
+ computeSearching(navigator.searchValue, navigator.searchMinLength),
31
+ navigator.searchValue,
32
+ renderLevel(navigator.openNodePath, navigator.tree),
33
+ navigator.tree
34
+ );
36
35
  // then I can see the contents of the node
37
36
  // and it is ordered folder-first, then alphabetically
38
- assert.equal(navigator.dataPlane[0].name, 'Node2');
39
- assert.equal(navigator.dataPlane[1].name, 'Node4');
40
- assert.equal(navigator.dataPlane[2].name, 'Node');
41
- assert.equal(navigator.dataPlane[3].name, 'Node');
42
- assert.equal(navigator.dataPlane.length, 4);
37
+ assert.equal(dataPlane[0].name, 'Data');
38
+ assert.equal(dataPlane[1].name, 'Backup');
39
+ assert.equal(dataPlane.length, 2);
40
+ });
41
+
42
+ test('set proper search placeholder', () => {
43
+ assert.equal(
44
+ navigator.shadowRoot.querySelector('cosmoz-input')
45
+ .shadowRoot
46
+ .querySelector('input').placeholder,
47
+ 'Custom search placeholder');
48
+ });
49
+
50
+ test('compute search if value is bigger than searchMinLength', () => {
51
+ assert.equal(computeSearching('long string', navigator.searchMinLength), true);
52
+ assert.equal(computeSearching('a', navigator.searchMinLength), false);
53
+ });
54
+
55
+ test('should check if node has children', () => {
56
+ const node = {
57
+ name: 'D:',
58
+ path: '1000',
59
+ children: { 1001: { name: 'Data', pathLocator: '1000.1001', children: { 1002: { name: 'John', pathLocator: '1000.1001.1002' }}}},
60
+ parentSectionName: 'D:',
61
+ pathLocator: '1000'
62
+ };
63
+ assert.equal(hasChildren(node), true);
64
+ const node2 = { name: 'Git', path: '1.5.7', parentSectionName: 'C:/Program Files', pathLocator: '1.5.7' };
65
+ assert.equal(hasChildren(node2), false);
43
66
  });
44
67
 
45
68
  test('match search string', () => {
46
69
  // given a node navigator with some data
47
- // when I search for 'ode2'
48
- navigator.searchValue = 'ode2';
49
- flush();
70
+ // when I search for 'John'
71
+ navigator.searchValue = 'John';
50
72
 
51
- //then I can see only 1 item matching 'ode2'
52
- assert.lengthOf(navigator.dataPlane, 1);
53
- assert.equal(navigator.dataPlane[0].name, 'Node2');
73
+ const dataPlane = computeDataPlane(
74
+ computeSearching(navigator.searchValue, navigator.searchMinLength),
75
+ navigator.searchValue,
76
+ renderLevel('1000', navigator.tree),
77
+ navigator.tree
78
+ );
79
+
80
+ //then I can see only 1 item matching 'John'
81
+ assert.lengthOf(dataPlane, 1);
82
+ assert.equal(dataPlane[0].name, 'John');
54
83
 
55
84
  });
56
85
 
@@ -58,9 +87,14 @@ suite('cosmoz-treenode-navigator', () => {
58
87
  // given a node navigator with some data
59
88
  // when I search for 'NONEXISTING'
60
89
  navigator.searchValue = 'NONEXISTING';
61
- flush();
90
+ const dataPlane = computeDataPlane(
91
+ computeSearching(navigator.searchValue, navigator.searchMinLength),
92
+ navigator.searchValue,
93
+ renderLevel('1000', navigator.tree),
94
+ navigator.tree
95
+ );
62
96
 
63
97
  //then I can see that is no contents
64
- assert.lengthOf(navigator.dataPlane, 0);
98
+ assert.lengthOf(dataPlane, 0);
65
99
  });
66
100
  });
@@ -1,61 +1,118 @@
1
1
  export default {
2
2
  1: {
3
- id: '11111111-1111-1111-1111-111111111111',
4
- name: 'Root',
3
+ name: 'C:',
5
4
  pathLocator: '1',
6
5
  children: {
7
- 6: {
8
- id: '2b577550-b874-4228-9395-a4fb00f31245',
9
- name: 'Node',
10
- pathLocator: '1.6',
11
- children: {}
12
- },
13
- 7: {
14
- id: '2b577550-b874-4228-9395-a4fb00f31245',
15
- name: 'Node',
16
- pathLocator: '1.7',
17
- children: {}
6
+ 2: {
7
+ name: 'Windows',
8
+ pathLocator: '1.2',
9
+ children: {
10
+ 3: {
11
+ name: 'System',
12
+ pathLocator: '1.2.3',
13
+ children: {}
14
+ },
15
+ 4: {
16
+ name: 'Microsoft.NET',
17
+ pathLocator: '1.2.4',
18
+ children: {}
19
+ }
20
+ }
18
21
  },
19
- 4: {
20
- id: '177d1485-7d4f-4c7d-86cd-a4fb00f31245',
21
- name: 'Node4',
22
- pathLocator: '1.2.3.4',
22
+ 5: {
23
+ name: 'Program Files',
24
+ pathLocator: '1.5',
23
25
  children: {
24
- 5: {
25
- id: '3a7754f1-e3e6-49c7-b6a8-a4fb00f31245',
26
- name: 'Node5',
27
- pathLocator: '1.2.3.4.5',
28
- children: {
29
- 501: {
30
- id: '3a7754f1-e3e6-49c7-b6a8-a4fb00f31245',
31
- name: 'Node501',
32
- pathLocator: '1.2.3.4.5.501',
33
- children: {}
34
- }
35
- }
26
+ 6: {
27
+ name: 'Microsoft VS Code',
28
+ pathLocator: '1.5.6'
29
+ },
30
+ 7: {
31
+ name: 'Git',
32
+ pathLocator: '1.5.7'
36
33
  }
37
34
  }
38
35
  },
39
- 2: {
40
- id: '167d1485-7d4f-4c7d-86cd-a4fb00f31245',
41
- name: 'Node2',
42
- pathLocator: '1.2',
36
+ 100: {
37
+ name: 'Users',
38
+ pathLocator: '1.100',
43
39
  children: {
44
- 3: {
45
- id: '3a7654f1-e3e6-49c7-b6a8-a4fb00f31245',
46
- name: 'Node3',
47
- pathLocator: '1.2.3',
40
+ 200: {
41
+ name: 'Default',
42
+ pathLocator: '1.100.200',
43
+ children: {
44
+ 201: {
45
+ name: 'Documents',
46
+ pathLocator: '1.100.200.201'
47
+ },
48
+ 202: {
49
+ name: 'Music',
50
+ pathLocator: '1.100.200.202'
51
+ },
52
+ 203: {
53
+ name: 'Pictures',
54
+ pathLocator: '1.100.200.203'
55
+ }
56
+ }
57
+ },
58
+ 300: {
59
+ name: 'John',
60
+ pathLocator: '1.100.300',
48
61
  children: {
49
62
  301: {
50
- id: '3a7654f1-e3e6-49c7-b6a8-a4fb00f31245',
51
- name: 'Node301',
52
- pathLocator: '1.2.3.301',
53
- children: {}
63
+ name: 'Documents',
64
+ pathLocator: '1.100.300.301'
65
+ },
66
+ 302: {
67
+ name: 'Music',
68
+ pathLocator: '1.100.300.302'
69
+ },
70
+ 303: {
71
+ name: 'Pictures',
72
+ pathLocator: '1.100.300.303'
73
+ }
74
+ }
75
+ },
76
+ 400: {
77
+ name: 'Public',
78
+ pathLocator: '1.100.400',
79
+ children: {
80
+ 401: {
81
+ name: 'Public Documents',
82
+ pathLocator: '1.100.400.401'
83
+ },
84
+ 402: {
85
+ name: 'Public Music',
86
+ pathLocator: '1.100.400.402'
87
+ },
88
+ 403: {
89
+ name: 'Public Pictures',
90
+ pathLocator: '1.100.400.403'
54
91
  }
55
92
  }
56
93
  }
57
94
  }
58
95
  }
59
96
  }
97
+ },
98
+ 1000: {
99
+ name: 'D:',
100
+ pathLocator: '1000',
101
+ children: {
102
+ 1001: {
103
+ name: 'Data',
104
+ pathLocator: '1000.1001',
105
+ children: {
106
+ 1002: {
107
+ name: 'John',
108
+ pathLocator: '1000.1001.1002'
109
+ }
110
+ }
111
+ },
112
+ 1002: {
113
+ name: 'Backup',
114
+ pathLocator: '1000.1002'
115
+ }
116
+ }
60
117
  }
61
118
  };
@@ -1,28 +1 @@
1
- import { chromeLauncher } from '@web/test-runner';
2
- import { seleniumLauncher } from '@web/test-runner-selenium';
3
- import webdriver from 'selenium-webdriver';
4
- import firefox from 'selenium-webdriver/firefox.js';
5
-
6
- export default {
7
- nodeResolve: true,
8
- coverageConfig: {
9
- reportDir: 'coverage',
10
- threshold: {
11
- statements: 70,
12
- branches: 70,
13
- functions: 50,
14
- lines: 70
15
- }
16
- },
17
- files: [
18
- '**!(node_modules)/*.test.js'
19
- ],
20
- testFramework: { config: { ui: 'tdd' }},
21
- browsers: [
22
- chromeLauncher(),
23
- seleniumLauncher({
24
- driverBuilder: new webdriver.Builder().forBrowser('firefox').setFirefoxOptions(new firefox.Options().headless())
25
- })
26
- ]
27
-
28
- };
1
+ export { default } from '@neovici/cfg/web/test-runner.mjs';