提交 25a0211b 编写于 作者: P Piotr Bryk 提交者: GitHub

Initial navigation menu for desktops (#1134)

* Initial navigation menu for desktops

And removal of middleellipsis logic to make this all work. Previous
middleellipsis implementation didn'q quite like that we have menu
sliding from the left.
上级 e6cf9e0d
......@@ -550,4 +550,6 @@
<translation id="2492306272014913632" key="MSG_SECRET_INFO_NAME_ENTRY" source="/usr/local/google/home/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Config map info details section name entry.">Name</translation>
<translation id="2646286682599680700" key="MSG_SECRET_INFO_NAMESPACE_ENTRY" source="/usr/local/google/home/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Config map info details section namespace entry.">Namespace</translation>
<translation id="849285697167851134" key="MSG_SECRET_INFO_LABELS_ENTRY" source="/usr/local/google/home/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Config map info details section labels entry.">Labels</translation>
<translation id="3762614397911712807" key="MSG_NODE_DETAIL_PODS_ZEROSTATE_TITLE" source="/usr/local/google/home/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Title for pods card zerostate in node details page.">There is nothing to display here</translation>
<translation id="4320636739666887610" key="MSG_NODE_DETAIL_PODS_ZEROSTATE_TEXT" source="/usr/local/google/home/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Text for pods card zerostate in node details page.">There are currently no Pods scheduled on this Node</translation>
</translationbundle>
\ No newline at end of file
......@@ -739,4 +739,6 @@
<translation id="2492306272014913632" key="MSG_SECRET_INFO_NAME_ENTRY" source="/usr/local/google/home/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Config map info details section name entry.">Name</translation>
<translation id="2646286682599680700" key="MSG_SECRET_INFO_NAMESPACE_ENTRY" source="/usr/local/google/home/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Config map info details section namespace entry.">Namespace</translation>
<translation id="849285697167851134" key="MSG_SECRET_INFO_LABELS_ENTRY" source="/usr/local/google/home/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Config map info details section labels entry.">Labels</translation>
</translationbundle>
<translation id="3762614397911712807" key="MSG_NODE_DETAIL_PODS_ZEROSTATE_TITLE" source="/usr/local/google/home/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Title for pods card zerostate in node details page.">There is nothing to display here</translation>
<translation id="4320636739666887610" key="MSG_NODE_DETAIL_PODS_ZEROSTATE_TEXT" source="/usr/local/google/home/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Text for pods card zerostate in node details page.">There are currently no Pods scheduled on this Node</translation>
</translationbundle>
\ No newline at end of file
......@@ -14,11 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<md-toolbar class="kd-toolbar">
<md-toolbar>
<div class="md-toolbar-tools kd-toolbar-tools">
<a ng-href="{{$ctrl.getLogoHref()}}" tabindex="-1">
<md-icon md-svg-icon="assets/images/kubernetes-logo.svg" class="kd-toolbar-logo"></md-icon>
</a>
<kd-nav-hamburger></kd-nav-hamburger>
<h2>
<span>kubernetes</span>
</h2>
......@@ -27,16 +25,20 @@ limitations under the License.
</div>
</md-toolbar>
<kd-actionbar ng-if="$ctrl.isActionbarVisible()" flex="none"></kd-actionbar>
<md-content flex="auto" class="kd-app-content">
<div ng-switch="$ctrl.showLoadingSpinner" flex="auto" >
<div ng-switch-when="true" class="kd-center-fixed">
<md-progress-circular md-mode="indeterminate" md-diameter="96">
</md-progress-circular>
</div>
<div ng-switch-when="false" class="kd-app-content">
<div ui-view class="md-body-1"></div>
</div>
<div layout="row">
<kd-nav></kd-nav>
<div layout="column" flex="shrink" class="kd-chrome-content-container">
<kd-actionbar ng-if="$ctrl.isActionbarVisible()" flex="none"></kd-actionbar>
<md-content flex="auto" class="kd-app-content">
<div ng-switch="$ctrl.showLoadingSpinner" flex="auto" >
<div ng-switch-when="true" class="kd-center-fixed">
<md-progress-circular md-mode="indeterminate" md-diameter="96">
</md-progress-circular>
</div>
<div ng-switch-when="false" class="kd-app-content">
<div ui-view class="md-body-1"></div>
</div>
</div>
</md-content>
</div>
</md-content>
</div>
......@@ -58,6 +58,7 @@ a {
.kd-toolbar-tools {
margin: auto;
padding-left: 0;
}
.kd-app-content {
......@@ -77,6 +78,10 @@ a {
top: 0;
}
.kd-chrome-content-container {
width: 100%;
}
.kd-text-icon {
font-size: inherit;
height: inherit;
......
......@@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {stateName as workloadState} from 'workloads/workloads_state';
import {actionbarViewName} from './chrome_state';
/**
......@@ -44,12 +42,6 @@ export class ChromeController {
/** @export */
$onInit() { this.registerStateChangeListeners(this.scope_); }
/**
* @return {string}
* @export
*/
getLogoHref() { return this.state_.href(workloadState); }
/**
* @return {boolean}
* @export
......
......@@ -16,6 +16,7 @@ import stateConfig from './chrome_stateconfig';
import {chromeComponent} from './chrome_component';
import componentsModule from 'common/components/components_module';
import namespaceModule from 'common/namespace/namespace_module';
import navModule from './nav/module';
/**
* Angular module containing navigation chrome for the application.
......@@ -28,6 +29,7 @@ export default angular
'ui.router',
componentsModule.name,
namespaceModule.name,
navModule.name,
])
.config(stateConfig)
.component('kdChrome', chromeComponent);
<!--
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-button ng-click="$ctrl.toggle()" class="md-icon-button">
<md-icon md-font-library="material-icons">menu</md-icon>
</md-button>
......@@ -13,37 +13,24 @@
// limitations under the License.
/**
* Returns filter function to apply middle ellipsis when string is longer then max parameter.
* @return {function(string, number): string}
* @final
*/
export default function middleEllipsisFilter() {
export class HamburgerController {
/**
* Filter function to apply middle ellipsis when string is longer then max parameter.
* @param {string} value Filtered value.
* @param {number} limit Length limit for filtered value.
* @return {string}
* @param {!./nav_service.NavService} kdNavService
* @ngInject
*/
let filterFunction = function(value, limit) {
limit = parseInt(limit, 10);
if (!limit) return '';
if (!value) return value;
if (value.length <= limit) return value;
if (limit === 1) return `${value[0]}...`;
constructor(kdNavService) {
/** @private {!./nav_service.NavService} */
this.kdNavService_ = kdNavService;
}
/**
* Begin part of truncated text.
* @type {number}
*/
let beginPartIndex = Math.floor(limit / 2 + limit % 2);
/**
* End part of truncated text.
* @type {number}
*/
let endPartIndex = -Math.floor(limit / 2);
let begin = value.substring(0, beginPartIndex);
let end = value.slice(endPartIndex);
return `${begin}...${end}`;
};
return filterFunction;
/** @export */
toggle() { this.kdNavService_.toggle(); }
}
/** @type {!angular.Component} */
export const hamburgerComponent = {
controller: HamburgerController,
templateUrl: 'chrome/nav/hamburger.html',
};
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {hamburgerComponent} from './hamburger_component';
import {navComponent} from './nav_component';
import {NavService} from './nav_service';
/**
* Angular module containing navigation for the application.
*/
export default angular
.module(
'kubernetesDashboard.chrome.nav',
[
'ngMaterial',
])
.service('kdNavService', NavService)
.component('kdNavHamburger', hamburgerComponent)
.component('kdNav', navComponent);
<!--
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-sidenav md-is-locked-open="$ctrl.isVisible" class="kd-nav">
Navigation_drawer_here
</md-sidenav>
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
@import '../../variables';
.kd-nav {
background-color: initial;
height: 100%;
overflow: hidden;
}
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @final
*/
export class NavController {
/**
* @param {!./nav_service.NavService} kdNavService
* @ngInject
*/
constructor(kdNavService) {
/** @export {boolean} */
this.isVisible = true;
/** @private {!./nav_service.NavService} */
this.kdNavService_ = kdNavService;
}
/** @export */
$onInit() { this.kdNavService_.registerNav(this); }
/**
* Toggles visibility of the navigation component.
*/
toggle() { this.isVisible = !this.isVisible; }
}
/**
* @type {!angular.Component}
*/
export const navComponent = {
controller: NavController,
templateUrl: 'chrome/nav/nav.html',
};
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @final
*/
export class NavService {
/**
* @ngInject
*/
constructor() {
/** @private {./nav_component.NavController} */
this.navComponent_ = null;
}
/**
* @param {!./nav_component.NavController} navComponent
*/
registerNav(navComponent) { this.navComponent_ = navComponent; }
/**
*/
toggle() {
if (this.navComponent_) {
this.navComponent_.toggle();
} else {
throw new Error('Navigation menu is not registered. This is likely a programming error.');
}
}
}
......@@ -16,7 +16,7 @@ limitations under the License.
<md-toolbar class="kd-toolbar kd-actionbar">
<div class="md-toolbar-tools" layout="row">
<kd-breadcrumbs class="kd-actionbar-breadcrumbs" limit="2" flex="auto"></kd-breadcrumbs>
<kd-breadcrumbs class="kd-actionbar-breadcrumbs" limit="2" flex="auto" layout="row"></kd-breadcrumbs>
<span flex="auto"></span>
<div ui-view="actionbar"></div>
</div>
......
......@@ -29,7 +29,6 @@
}
.kd-actionbar-breadcrumbs {
flex-grow: 2;
max-width: 100%;
overflow: hidden;
white-space: nowrap;
......
......@@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<h1 ng-switch="$last" ng-repeat="breadcrumb in ::ctrl.breadcrumbs" class="md-title"
style="display: inline">
<div ng-switch="$last" ng-repeat="breadcrumb in ::ctrl.breadcrumbs" class="md-title kd-breadcrumbs-ellipsised"
ng-class="{'kd-inline-breadcrumb': !$last}" flex="shrink">
<a ng-switch-when="false" href="{{::breadcrumb.stateLink}}">
{{::breadcrumb.label}}
</a>
<span ng-switch-when="true" class="kd-breadcrumbs-ellipsised">
<div ng-switch-when="true" class="kd-breadcrumbs-ellipsised">
<kd-middle-ellipsis display-string="{{::breadcrumb.label}}"></kd-middle-ellipsis>
</span>
</div>
<span ng-switch-when="false">></span>
</span>
</div>
......@@ -12,7 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
@import '../../../variables';
.kd-breadcrumbs-ellipsised {
display: inline-block;
width: 100%;
float: left;
max-width: 100%;
}
.kd-inline-breadcrumb {
flex-basis: auto;
flex-grow: 0;
padding-right: $baseline-grid / 2;
}
......@@ -15,10 +15,11 @@ limitations under the License.
-->
<div ng-if="::labelsCtrl.labels">
<kd-middle-ellipsis display-string="{{::key}}: {{::value}}" class="kd-labels"
ng-repeat="(key, value) in ::labelsCtrl.labels"
ng-if="labelsCtrl.isVisible($index)">
</kd-middle-ellipsis>
<div ng-repeat="(key, value) in ::labelsCtrl.labels"
ng-if="labelsCtrl.isVisible($index)" class="kd-labels">
<kd-middle-ellipsis display-string="{{::key}}: {{::value}}" class="kd-ellipsis-label">
</kd-middle-ellipsis>
</div>
<div class="kd-labels kd-labels-switch" ng-show="labelsCtrl.isMoreAvailable()"
ng-click="labelsCtrl.switchLabelsView()">
{{labelsCtrl.isShowingAll() ?
......
......@@ -22,6 +22,11 @@ $label-margin: 0 $baseline-grid ($baseline-grid / 2) 0;
border-radius: $baseline-grid;
display: inline-block;
margin: $label-margin;
max-width: 100%;
}
.kd-ellipsis-label {
display: block;
padding: ($baseline-grid / 4) $baseline-grid;
}
......
......@@ -17,4 +17,4 @@ limitations under the License.
<md-tooltip md-direction="top" ng-if="ellipsisCtrl.shouldTruncate()">
{{ellipsisCtrl.displayString}}
</md-tooltip>
<span class="kd-middleellipsis"></span>
<div class="kd-middleellipsis">{{ellipsisCtrl.displayString}}</div>
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* Based on given container and its width calculates maximum text length.
* Filter is used to truncate string until binary search finds optimal length to use
* available space.
*
* Container is outer element of 'element' and is used as bounding box to allow stretching of
* inner element and not exceed boundaries of outer container. It's the best to style container
* with display: [block|inline-block] and width: 100% and disable text wrapping for the element.
*
* @param {number} availableWidth - the width of outer container element that is available
* for filling content into
* @param {!Element} element - element that content text will be added to
* @param {!Element} measurementElement - element that width will be measured when ellipsing
* the text
* @param {!function(string, number): string} filter - middle ellipsis filter function used to
* truncate string
* @param {string} displayString - original display string (not truncated)
* @return {number}
*/
export default function computeTextLength(
availableWidth, element, measurementElement, filter, displayString) {
// Does binary search to find minimal integer I such that given string with length I fits into
// available space and with length I + 1 it does not.
// Make right displayString.length * 2 to start binary search with max length, which should be
// most common case.
let [left, right] = [0, displayString.length * 2];
let [width, length] = [0, 0];
while (left <= right) {
length = Math.ceil((left + right) / 2);
element.textContent = filter(displayString, length);
width = measurementElement.offsetWidth;
if (width < availableWidth) {
left = length + 1;
} else if (width > availableWidth) {
right = length - 1;
} else {
break;
}
if (left > right && width > availableWidth && length > 0) {
element.textContent = filter(displayString, --length);
}
}
return Math.min(length, displayString.length);
}
......@@ -13,5 +13,7 @@
// limitations under the License.
.kd-middleellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
......@@ -13,18 +13,14 @@
// limitations under the License.
import MiddleEllipsisController from './middleellipsis_controller';
import computeTextLength from './middleellipsis';
/**
* Returns directive definition for middle ellipsis.
*
* @param {!function(string, number): string} middleEllipsisFilter
* @param {!angular.$window} $window
*
* @return {!angular.Directive}
* @ngInject
*/
export default function middleEllipsisDirective(middleEllipsisFilter, $window) {
export default function middleEllipsisDirective() {
return {
controller: MiddleEllipsisController,
controllerAs: 'ellipsisCtrl',
......@@ -33,41 +29,5 @@ export default function middleEllipsisDirective(middleEllipsisFilter, $window) {
bindToController: {
'displayString': '@',
},
/**
* @param {!angular.Scope} scope
* @param {!angular.JQLite} jQLiteElem
* @param {!angular.Attributes} attr
* @param {!MiddleEllipsisController} ctrl
*/
link: function(scope, jQLiteElem, attr, ctrl) {
/** @type {!Element} */
let element = jQLiteElem[0];
let container = element.parentElement;
let ellipsisElem = element.querySelector('.kd-middleellipsis');
if (!container) {
throw new Error(`Required parent container not found`);
}
if (!ellipsisElem) {
throw new Error('Required element with class .kd-middleellipsis not found');
}
let nonNullElement = ellipsisElem;
angular.element($window).bind('resize', recalculateTextLength);
function recalculateTextLength() {
ctrl.maxLength = computeTextLength(
container.offsetWidth, nonNullElement, element, middleEllipsisFilter,
ctrl.displayString);
}
recalculateTextLength();
scope.$on('$destroy', () => {
angular.element($window).unbind('ready', recalculateTextLength);
angular.element($window).unbind('resize', recalculateTextLength);
});
},
};
}
......@@ -15,7 +15,6 @@
import coresFilter from './cores_filter';
import itemsPerPageFilter from './itemsperpage_filter';
import memoryFilter from './memory_filter';
import middleEllipsisFilter from './middleellipsis_filter';
import relativeTimeFilter from './relativetime_filter';
import appConfigModule from '../appconfig/appconfig_module';
......@@ -30,7 +29,6 @@ export default angular
'angularUtils.directives.dirPagination',
appConfigModule.name,
])
.filter('middleEllipsis', middleEllipsisFilter)
.filter('kdMemory', memoryFilter)
.filter('kdCores', coresFilter)
.filter('relativeTime', relativeTimeFilter)
......
......@@ -22,14 +22,14 @@ limitations under the License.
</kd-info-card-entry>
<kd-info-card-entry title="{{::$ctrl.i18n.MSG_CONTAINER_DETAILS_ENV_VARS}}">
<div ng-if="::container.env.length">
<div ng-repeat="env in ::container.env">
{{::env.name}}<span ng-if="env.valueFrom">
<div ng-repeat="env in ::container.env" layout="row">
<span>{{::env.name}}</span><span ng-if="env.valueFrom">
<span ng-if="env.valueFrom.configMapKeyRef">
(<a ng-href="{{::$ctrl.getEnvConfigMapHref(env.valueFrom.configMapKeyRef)}}"><!--
-->{{::$ctrl.i18n.getConfigMapLabel(env.valueFrom.configMapKeyRef)}}</a>)
</span>
</span>:
<kd-middle-ellipsis display-string="{{::env.value}}"><kd-middle-ellipsis>
</span><span>:&nbsp;</span>
<kd-middle-ellipsis display-string="{{::env.value}}" flex="shrink"><kd-middle-ellipsis>
</div>
</div>
<div ng-if="::!container.env.length">
......
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import module from 'chrome/nav/module';
describe('Hamburger component', () => {
/** @type {!chrome/nav/nav_component.NavController} */
let ctrl;
/** @type {!chrome/nav/nav_service.NavService} */
let navService;
beforeEach(() => {
angular.mock.module(module.name);
angular.mock.inject(($componentController, $rootScope, kdNavService) => {
ctrl = $componentController('kdNavHamburger', {$scope: $rootScope});
navService = kdNavService;
});
});
it('should use kdNavService and toggle by it', () => {
spyOn(navService, 'toggle');
ctrl.toggle();
expect(navService.toggle).toHaveBeenCalled();
});
});
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import module from 'chrome/nav/module';
describe('Nav component', () => {
/** @type {!chrome/nav/nav_component.NavController} */
let ctrl;
/** @type {!chrome/nav/nav_service.NavService} */
let navService;
beforeEach(() => {
angular.mock.module(module.name);
angular.mock.inject(($componentController, $rootScope, kdNavService) => {
ctrl = $componentController('kdNav', {$scope: $rootScope});
ctrl.$onInit();
navService = kdNavService;
});
});
it('should use kdNavService and toggle by it', () => {
// initial state assert
expect(ctrl.isVisible).toBe(true);
navService.toggle();
expect(ctrl.isVisible).toBe(false);
});
});
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import module from 'chrome/nav/module';
describe('Nav service', () => {
/** @type {!chrome/nav/nav_service.NavService} */
let navService;
beforeEach(() => angular.mock.module(module.name));
beforeEach(angular.mock.inject((kdNavService) => { navService = kdNavService; }));
it('should deny toggle when not initialized', () => {
expect(() => {
navService.toggle();
}).toThrow(new Error('Navigation menu is not registered. This is likely a programming error.'));
});
it('toggle nav', () => {
let comp = jasmine.createSpy('comp');
navService.registerNav({toggle: comp});
navService.toggle();
expect(comp).toHaveBeenCalled();
});
});
......@@ -42,7 +42,7 @@ describe('Labels directive', () => {
scope.$digest();
// then
let labels = element.find('span');
let labels = element.find('kd-middle-ellipsis');
expect(labels.length).toEqual(3);
angular.forEach(
(key, value, index) => { expect(labels.eq(index).text()).toBe(`${key}=${value}`); });
......
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import componentsModule from 'common/components/components_module';
describe('Middle ellipsis directive', () => {
/** @type {angular.Scope} */
let scope;
/** @type {function(!angular.Scope):!angular.JQLite} */
let compileFn;
/** @type {angular.$window} */
let window;
/** @type {Event} **/
let resizeEvent;
beforeEach(() => {
angular.mock.module(componentsModule.name);
angular.mock.inject(($rootScope, $compile, $window) => {
resizeEvent = $window.document.createEvent('UIEvent');
resizeEvent.initEvent('resize', true, false);
scope = $rootScope.$new();
window = $window;
compileFn = $compile(`<div><kd-middle-ellipsis display-string="{{displayString}}"
max-length="{{maxLength}}"></kd-middle-ellipsis></div>`);
});
});
it('should show original display string', () => {
// given
let stringLength = 16;
scope.displayString = new Array(stringLength + 1).join('x');
let element = compileFn(scope);
document.body.appendChild(element[0]);
scope.$digest();
// when
element[0].style.width = '500px';
window.dispatchEvent(resizeEvent);
scope.$digest();
// then
expect(element.text().trim().length).toEqual(stringLength);
// when
element[0].style.width = '1px';
window.dispatchEvent(resizeEvent);
scope.$digest();
// then
expect(element.text().trim().length).toEqual(0);
// when
element[0].style.width = '50px';
window.dispatchEvent(resizeEvent);
scope.$digest();
// then
expect(element.text().trim().length).toEqual(7);
});
});
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import computeTextLength from 'common/components/middleellipsis/middleellipsis';
import componentsModule from 'common/components/components_module';
describe('Middle ellipsis', () => {
/** @type {!angular.Scope} */
let scope;
/** @type {!angular.$compile} */
let compile;
/** @type {!angular.$filter} */
let filter;
/**
* Creates fixture html element mock for the tests. If given string is 1 char wide then final
* string length will be extended by given text length number, otherwise given string will be
* used.
*
* @param {number} availableWidth
* @param {string} str
* @param {number} textLength
*
* @return {!Array<!angular.JQLite|number>}
*/
function getFixtureElement(availableWidth, str, textLength) {
let displayText;
if (str.length === 1) {
displayText = new Array(textLength + 1).join(str);
} else {
displayText = str;
}
return [
angular.element(
`<div style="width: ${availableWidth}px; display: block; overflow: hidden;" id="fixture">
<div style="width: 100%; display: block;" class="kd-middleellipsis-container">
<span class="kd-middleellipsis">${displayText}</span>
</div>
</div>`),
availableWidth,
];
}
beforeEach(() => {
angular.mock.module(componentsModule.name);
angular.mock.inject(($compile, $rootScope, middleEllipsisFilter) => {
scope = $rootScope.$new();
compile = $compile;
filter = middleEllipsisFilter;
});
});
it('should compute optimal text length', () => {
// given
/**
* Test object contains:
* - element - fixture element that contains kd-middleellipsis-container and
* kd-middleellipsis elements.
* - filterFn - filter function used to truncate string
* - expected - expected length calculated by computeTextLength function
*/
let testData = [
// One 'x' char takes approx. 8px. When result length is 0 string also won't be truncated.
// It's important to remember that '...' also take some space. One '.' take approx. 4px.
[getFixtureElement(0, 'x', 1), filter, 0],
[getFixtureElement(8, 'x', 1), filter, 1],
[getFixtureElement(14, 'x', 2), filter, 0],
[getFixtureElement(16, 'x', 2), filter, 2],
[getFixtureElement(22, 'x', 3), filter, 1],
[getFixtureElement(24, 'x', 3), filter, 3],
[getFixtureElement(24, 'x', 4), filter, 1],
[getFixtureElement(30, 'x', 4), filter, 2],
[getFixtureElement(32, 'x', 4), filter, 4],
[getFixtureElement(26, 'x', 33), filter, 1],
[getFixtureElement(30, 'x', 33), filter, 2],
[getFixtureElement(323, 'x', 66), filter, 38],
[getFixtureElement(324, 'x', 57), filter, 39],
[getFixtureElement(555, 'dashboard', 0), filter, 9],
[getFixtureElement(20, 'dashboard', 0), filter, 1],
];
testData.forEach((testData) => {
// given
let [[fixture, availableWidth], filter, expected] = testData;
fixture = compile(fixture)(scope);
angular.element('body').append(fixture);
let element = fixture.find('.kd-middleellipsis')[0];
let displayText = element.textContent;
// when
let actual = computeTextLength(availableWidth, element, element, filter, displayText);
// then
expect(actual).toBe(
expected, `Expected length to be ${expected} but was ${actual} for text: ${displayText}`);
// Clean up
fixture.remove();
});
});
});
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import filtersModule from 'common/filters/filters_module';
describe('Apply ellipsis filter', () => {
const testedString = 'podName';
beforeEach(function() { angular.mock.module(filtersModule.name); });
it('has a applyMiddleEllipsis filter',
angular.mock.inject(function($filter) { expect($filter('middleEllipsis')).not.toBeNull(); }));
it('should return empty value if max parameter is undefined',
angular.mock.inject(function(middleEllipsisFilter) {
expect(middleEllipsisFilter(testedString)).toEqual('');
}));
it('should return the same value if length less then given max length parameter',
angular.mock.inject(function(middleEllipsisFilter) {
expect(middleEllipsisFilter(testedString, 10)).toEqual('podName');
}));
it('should return empty value when max = 0', angular.mock.inject(function(middleEllipsisFilter) {
expect(middleEllipsisFilter(testedString, 0)).toEqual('');
}));
it('should return truncated value with ellipsis as tail',
angular.mock.inject(function(middleEllipsisFilter) {
expect(middleEllipsisFilter(testedString, 1)).toEqual('p...');
}));
it('should return truncated value with the ellipsis in the middle',
angular.mock.inject(function(middleEllipsisFilter) {
expect(middleEllipsisFilter(testedString, 2)).toEqual('p...e');
}));
it('should return truncated value with the ellipsis in the middle',
angular.mock.inject(function(middleEllipsisFilter) {
expect(middleEllipsisFilter(testedString, 3)).toEqual('po...e');
}));
it('should return truncated value with the ellipsis in the middle',
angular.mock.inject(function(middleEllipsisFilter) {
expect(middleEllipsisFilter(testedString, 5)).toEqual('pod...me');
}));
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册