Upgrade TimezoneService

pull/6263/head
Oliver Günther 7 years ago
parent 3f4e854ad6
commit 7ca70709e9
No known key found for this signature in database
GPG Key ID: 88872239EB414F99
  1. 8
      frontend/app/angular4-modules.ts
  2. 1
      frontend/app/angular4-transition-utils.ts
  3. 7
      frontend/app/components/common/authoring/authoring.component.ts
  4. 34
      frontend/app/components/common/date/op-date-time.component.ts
  5. 46
      frontend/app/components/common/date/op-date-time.directive.ts
  6. 43
      frontend/app/components/common/urijs/urijs.service.test.ts
  7. 164
      frontend/app/components/datetime/timezone.service.ts
  8. 25
      frontend/app/components/filters/abstract-filter-date-time-value/abstract-filter-date-time-value.controller.ts
  9. 23
      frontend/app/components/filters/filter-date-time-value/filter-date-time-value.component.ts
  10. 14
      frontend/app/components/filters/filter-date-times-value/filter-date-times-value.component.ts
  11. 11
      frontend/app/components/filters/filter-date-value/filter-date-value.component.ts
  12. 9
      frontend/app/components/filters/filter-dates-value/filter-dates-value.component.ts
  13. 56
      frontend/app/components/input/transformers/transform-date-utc.directive.ts
  14. 64
      frontend/app/components/input/transformers/transform-date.directive.ts
  15. 56
      frontend/app/components/input/transformers/transform-duration.directive.ts
  16. 64
      frontend/app/components/modals/request-for-confirmation/request-for-confirmation.directive.test.ts
  17. 137
      frontend/app/components/work-packages/work-package-comment/work-package-comment.directive.test.js
  18. 71
      frontend/app/components/work-packages/work-package.service.test.js
  19. 130
      frontend/app/components/wp-attachments/wp-attachments-upload/wp-attachments-upload.directive.test.ts
  20. 6
      frontend/app/components/wp-display/field-types/wp-display-date-field.module.ts
  21. 6
      frontend/app/components/wp-display/field-types/wp-display-datetime-field.module.ts
  22. 10
      frontend/app/components/wp-display/field-types/wp-display-duration-field.module.ts
  23. 5
      frontend/app/components/wp-edit/op-date-picker/datepicker.ts
  24. 10
      frontend/app/components/wp-edit/op-date-picker/op-date-picker.component.ts
  25. 9
      frontend/app/components/wp-edit/op-date-picker/op-date-picker.directive.ts
  26. 171
      frontend/app/components/wp-query-menu/wp-query-menu.directive.ng2.test.ts
  27. 244
      frontend/app/components/wp-query-menu/wp-query-menu.directive.test.ts
  28. 18
      frontend/app/components/wp-query-menu/wp-query-menu.directive.ts
  29. 4
      frontend/app/components/wp-single-view-tabs/activity-panel/activity-entry-info.ts
  30. 4
      frontend/app/components/wp-single-view-tabs/activity-panel/wp-activity.service.ts
  31. 4
      frontend/app/components/wp-table/timeline/cells/timeline-cell-renderer.ts
  32. 5
      frontend/app/init-app.ts
  33. 2
      frontend/app/services/index.js
  34. 157
      frontend/app/services/timezone-service.js
  35. 10
      frontend/npm-shrinkwrap.json
  36. 1
      frontend/package.json
  37. 80
      frontend/tests/unit/tests/components/input/transformers/transform-date-test.js
  38. 128
      frontend/tests/unit/tests/components/input/transformers/transform-date-utc-test.js

@ -79,7 +79,6 @@ import {
settingsModalToken,
shareModalToken,
TextileServiceToken,
TimezoneServiceToken,
upgradeService,
upgradeServiceWithToken,
wpDestroyModalToken,
@ -128,7 +127,7 @@ import {AuthoringComponent} from 'core-components/common/authoring/authoring.com
import {Ng1WorkPackageAttachmentsUploadWrapper} from 'core-components/wp-attachments/wp-attachments-upload/wp-attachments-upload-ng1-wrapper';
import {WorkPackageAttachmentListComponent} from 'core-components/wp-attachments/wp-attachment-list/wp-attachment-list.component';
import {WorkPackageAttachmentListItemComponent} from 'core-components/wp-attachments/wp-attachment-list/wp-attachment-list-item.component';
import {OpDateTimeUpgradedDirective} from 'core-components/common/date/op-date-time.upgraded.directive';
import {OpDateTimeComponent} from 'core-components/common/date/op-date-time.component';
import {UserLinkComponent} from 'core-components/user/user-link/user-link.component';
import {WorkPackagesActivityService} from 'core-components/wp-single-view-tabs/activity-panel/wp-activity.service';
import {NewestActivityOnOverviewComponent} from 'core-components/wp-single-view-tabs/activity-panel/activity-on-overview.component';
@ -212,6 +211,7 @@ import {AttributeHelpTextsService} from 'core-components/common/help-texts/attri
import {UserCacheService} from 'core-components/user/user-cache.service';
import {WorkPackageWatchersService} from 'core-components/wp-single-view-tabs/watchers-tab/wp-watchers.service';
import {ProjectCacheService} from 'core-components/projects/project-cache.service';
import {TimezoneService} from 'core-components/datetime/timezone.service';
@NgModule({
imports: [
@ -241,7 +241,7 @@ import {ProjectCacheService} from 'core-components/projects/project-cache.servic
upgradeServiceWithToken('FocusHelper', FocusHelperToken),
upgradeServiceWithToken('PathHelper', PathHelperToken),
upgradeServiceWithToken('wpMoreMenuService', wpMoreMenuServiceToken),
upgradeServiceWithToken('TimezoneService', TimezoneServiceToken),
TimezoneService,
upgradeServiceWithToken('wpDestroyModal', wpDestroyModalToken),
upgradeServiceWithToken('shareModal', shareModalToken),
upgradeServiceWithToken('saveModal', saveModalToken),
@ -377,7 +377,7 @@ import {ProjectCacheService} from 'core-components/projects/project-cache.servic
Ng1WorkPackageAttachmentsUploadWrapper,
WorkPackageAttachmentListComponent,
WorkPackageAttachmentListItemComponent,
OpDateTimeUpgradedDirective,
OpDateTimeComponent,
UserLinkComponent,
ClickOnKeypressComponent,
WorkPackageFormQueryGroupComponent,

@ -49,7 +49,6 @@ export const FocusHelperToken = new InjectionToken<any>('FocusHelper');
export const NotificationsServiceToken = new InjectionToken<any>('NotificationsService');
export const PathHelperToken = new InjectionToken<any>('PathHelper');
export const wpMoreMenuServiceToken = new InjectionToken<any>('wpMoreMenuService');
export const TimezoneServiceToken = new InjectionToken<any>('TimezoneService');
export const $httpToken = new InjectionToken<any>('$http');
export const wpDestroyModalToken = new InjectionToken<any>('wpDestroyModal');
export const OpContextMenuLocalsToken = new InjectionToken<any>('CONTEXT_MENU_LOCALS');

@ -27,11 +27,12 @@
//++
import {Component, Inject, Input, OnInit} from '@angular/core';
import {I18nToken, TimezoneServiceToken} from '../../../angular4-transition-utils';
import {I18nToken} from '../../../angular4-transition-utils';
import {PathHelperService} from 'core-components/common/path-helper/path-helper.service';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {opUiComponentsModule} from 'core-app/angular-modules';
import {downgradeComponent} from '@angular/upgrade/static';
import {TimezoneService} from 'core-components/datetime/timezone.service';
@Component({
template: require('!!raw-loader!./authoring.html'),
@ -52,12 +53,12 @@ export class AuthoringComponent implements OnInit {
public constructor(readonly PathHelper:PathHelperService,
@Inject(I18nToken) readonly I18n:op.I18n,
@Inject(TimezoneServiceToken) readonly TimezoneService:any) {
readonly timezoneService:TimezoneService) {
}
ngOnInit() {
this.createdOnTime = this.TimezoneService.parseDatetime(this.createdOn);
this.createdOnTime = this.timezoneService.parseDatetime(this.createdOn);
this.timeago = this.createdOnTime.fromNow();
this.time = this.createdOnTime.format('LLL');
this.userLink = this.PathHelper.userPath(this.author.id);

@ -26,18 +26,36 @@
// See doc/COPYRIGHT.rdoc for more details.
//++
import {Component, Directive, ElementRef, Injector, Input, Output} from '@angular/core';
import {UpgradeComponent} from '@angular/upgrade/static';
import {UserResource} from 'core-app/modules/hal/resources/user-resource';
import {Component, Input} from '@angular/core';
import {TimezoneService} from 'core-components/datetime/timezone.service';
import {opUiComponentsModule} from 'core-app/angular-modules';
import {downgradeComponent} from '@angular/upgrade/static';
@Directive({
selector: 'op-date-time'
@Component({
selector: 'op-date-time',
template: `
<span title="{{date}} {{ time }}">
<span [textContent]="date"></span>
<span [textContent]="time"></span>
</span>'
`
})
export class OpDateTimeUpgradedDirective extends UpgradeComponent {
export class OpDateTimeComponent {
@Input('dateTimeValue') dateTimeValue:any;
constructor(elementRef:ElementRef, injector:Injector) {
super('opDateTime', elementRef, injector);
public date:any;
public time:any;
constructor(readonly timezoneService:TimezoneService) {
}
ngOnInit() {
var c = this.timezoneService.formattedDatetimeComponents(this.dateTimeValue);
this.date = c[0];
this.time = c[1];
}
}
opUiComponentsModule.directive('opDateTime',
downgradeComponent({ component: OpDateTimeComponent}));

@ -1,46 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
import {opUiComponentsModule} from '../../../angular-modules';
function opDateTimeDirective($compile:ng.ICompileService, TimezoneService:any) {
return {
restrict: 'EA',
scope: { dateTimeValue: '=' },
// Note: we cannot reuse op-date here as this does not apply the user's configured timezone
template: '<span title="{{ date }} {{ time }}"><span>{{date}}</span> <span>{{time}}</span></span>',
link: function(scope:any, element:ng.IAugmentedJQuery) {
var c = TimezoneService.formattedDatetimeComponents(scope.dateTimeValue);
scope.date = c[0];
scope.time = c[1];
$compile(element.contents())(scope);
}
};
}
opUiComponentsModule.directive('opDateTime', opDateTimeDirective);

@ -1,43 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
import {openprojectModule} from '../../../angular-modules';
describe('URI service', () => {
var URI:any;
beforeEach(angular.mock.module(openprojectModule.name));
beforeEach(angular.mock.inject(function (_URI_:any) {
URI = _URI_;
}));
it('should exist', () => {
expect(URI).to.exist;
});
});

@ -0,0 +1,164 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
import {Inject, Injectable} from '@angular/core';
import {I18nToken} from 'core-app/angular4-transition-utils';
import {ConfigurationService} from 'core-components/common/config/configuration.service';
import {opServicesModule} from 'core-app/angular-modules';
import {downgradeInjectable} from '@angular/upgrade/static';
@Injectable()
export class TimezoneService {
constructor(readonly ConfigurationService:ConfigurationService,
@Inject(I18nToken) readonly I18n:op.I18n) {
}
public setupLocale() {
moment.locale(I18n.locale);
}
/**
* Takes a utc date time string and turns it into
* a local date time moment object.
*/
public parseDatetime(datetime:string, format?:string) {
var d = moment.utc(datetime, format);
if (this.ConfigurationService.isTimezoneSet()) {
d.local();
d.tz(this.ConfigurationService.timezone());
}
return d;
}
public parseDate(date:string, format?:string) {
return moment(date, format);
}
/**
* Parses a string that is considered to be a local date and
* turns it into a utc date time moment object.
* 'Local' might mean the browsers default time zone or the one configured
* in the Configuration Service.
*
* @param {String} date
* @param {String} format
* @returns {Moment}
*/
public parseLocalDateTime(date:string, format?:string) {
var result;
format = format || this.getTimeFormat();
if (this.ConfigurationService.isTimezoneSet()) {
result = moment.tz(date, format!, this.ConfigurationService.timezone());
} else {
result = moment(date, format);
}
result.utc();
return result;
}
/**
* Parses the specified datetime and applies the user's configured timezone, if any.
*
* This will effectfully transform the [server] provided datetime object to the user's configured local timezone.
*
* @param {String} datetime in 'YYYY-MM-DDTHH:mm:ssZ' format
* @returns {Moment}
*/
public parseISODatetime(datetime:string) {
return this.parseDatetime(datetime, 'YYYY-MM-DDTHH:mm:ssZ');
}
public parseISODate(date:string) {
return this.parseDate(date, 'YYYY-MM-DD');
}
public formattedDate(date:string) {
var d = this.parseDate(date);
return d.format(this.getDateFormat());
}
public formattedTime(datetimeString:string) {
return this.parseDatetime(datetimeString).format(this.getTimeFormat());
}
public formattedDatetime(datetimeString:string) {
var c = this.formattedDatetimeComponents(datetimeString);
return c[0] + ' ' + c[1];
}
public formattedDatetimeComponents(datetimeString:string) {
var d = this.parseDatetime(datetimeString);
return [
d.format(this.getDateFormat()),
d.format(this.getTimeFormat())
];
}
public toHours(durationString:string) {
return Number(moment.duration(durationString).asHours().toFixed(2));
}
public formattedDuration(durationString:string) {
return this.I18n.t('js.units.hour', { count: this.toHours(durationString) });
}
public formattedISODate(date:any) {
return this.parseDate(date).format('YYYY-MM-DD');
}
public formattedISODateTime(datetime:any) {
return datetime.format();
}
public isValidISODate(date:any) {
return this.isValid(date, 'YYYY-MM-DD');
}
public isValidISODateTime(dateTime:string) {
return this.isValid(dateTime, 'YYYY-MM-DDTHH:mm:ssZ');
}
public isValid(date:string, dateFormat:string) {
var format = dateFormat || this.getDateFormat();
return moment(date, [format], true).isValid();
}
public getDateFormat() {
return this.ConfigurationService.dateFormatPresent() ? this.ConfigurationService.dateFormat() : 'L';
}
public getTimeFormat() {
return this.ConfigurationService.timeFormatPresent() ? this.ConfigurationService.timeFormat() : 'LT';
}
}
opServicesModule.service('timezoneService', downgradeInjectable(TimezoneService));

@ -28,32 +28,33 @@
import {Moment} from 'moment';
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
import {TimezoneService} from 'core-components/datetime/timezone.service';
export abstract class AbstractDateTimeValueController {
public filter:QueryFilterInstanceResource;
constructor(protected I18n:op.I18n,
protected TimezoneService:any) {
_.remove(this.filter.values as string[], value => !this.TimezoneService.isValidISODateTime(value));
protected timezoneService:TimezoneService) {
_.remove(this.filter.values as string[], value => !this.timezoneService.isValidISODateTime(value));
}
public abstract get lowerBoundary():Moment
public abstract get upperBoundary():Moment
public abstract get lowerBoundary():Moment|null;
public abstract get upperBoundary():Moment|null;
public isoDateParser(data:string) {
if (!this.TimezoneService.isValidISODate(data)) {
if (!this.timezoneService.isValidISODate(data)) {
return '';
}
var d = this.TimezoneService.parseLocalDateTime(data);
return this.TimezoneService.formattedISODateTime(d);
var d = this.timezoneService.parseLocalDateTime(data);
return this.timezoneService.formattedISODateTime(d);
}
public isoDateFormatter(data:string) {
if (!this.TimezoneService.isValidISODateTime(data)) {
if (!this.timezoneService.isValidISODateTime(data)) {
return '';
}
var d = this.TimezoneService.parseISODatetime(data);
return this.TimezoneService.formattedISODate(d);
var d = this.timezoneService.parseISODatetime(data);
return this.timezoneService.formattedISODate(d);
}
public get isTimeZoneDifferent() {
@ -75,10 +76,12 @@ export abstract class AbstractDateTimeValueController {
return this.I18n.t('js.filter.time_zone_converted.only_end',
{ to: this.upperBoundary.format('YYYY-MM-DD HH:mm') });
} else {
} else if(this.lowerBoundary) {
return this.I18n.t('js.filter.time_zone_converted.only_start',
{ from: this.lowerBoundary.format('YYYY-MM-DD HH:mm') });
}
return '';
}
}

@ -29,9 +29,10 @@
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
import {AbstractDateTimeValueController} from '../abstract-filter-date-time-value/abstract-filter-date-time-value.controller'
import {Component, EventEmitter, Inject, Input, OnDestroy, Output} from '@angular/core';
import {I18nToken, TimezoneServiceToken} from 'core-app/angular4-transition-utils';
import {I18nToken} from 'core-app/angular4-transition-utils';
import {DebouncedEventEmitter} from 'core-components/angular/debounced-event-emitter';
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
import {TimezoneService} from 'core-components/datetime/timezone.service';
@Component({
selector: 'filter-date-time-value',
@ -42,8 +43,8 @@ export class FilterDateTimeValueComponent extends AbstractDateTimeValueControlle
@Output() public filterChanged = new DebouncedEventEmitter<QueryFilterInstanceResource>(componentDestroyed(this));
constructor(@Inject(I18nToken) readonly I18n:op.I18n,
@Inject(TimezoneServiceToken) readonly TimezoneService:any) {
super(I18n, TimezoneService);
readonly timezoneService:TimezoneService) {
super(I18n, timezoneService);
}
ngOnDestroy() {
@ -54,20 +55,28 @@ export class FilterDateTimeValueComponent extends AbstractDateTimeValueControlle
return this.filter.values[0];
}
public get valueString() {
return this.filter.values[0].toString();
}
public set value(val) {
this.filter.values = [val as string];
this.filterChanged.emit(this.filter);
}
public get lowerBoundary() {
if (this.value && this.TimezoneService.isValidISODateTime(this.value)) {
return this.TimezoneService.parseDatetime(this.value);
if (this.value && this.timezoneService.isValidISODateTime(this.valueString)) {
return this.timezoneService.parseDatetime(this.valueString);
}
return null;
}
public get upperBoundary() {
if (this.value && this.TimezoneService.isValidISODateTime(this.value)) {
return this.TimezoneService.parseDatetime(this.value).add(24, 'hours');
if (this.value && this.timezoneService.isValidISODateTime(this.valueString)) {
return this.timezoneService.parseDatetime(this.valueString).add(24, 'hours');
}
return null;
}
}

@ -29,10 +29,10 @@
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
import {AbstractDateTimeValueController} from '../abstract-filter-date-time-value/abstract-filter-date-time-value.controller'
import {Component, Inject, Input, OnDestroy, Output} from '@angular/core';
import {TimezoneServiceToken} from 'core-app/angular4-transition-utils';
import {I18nToken} from '../../../angular4-transition-utils';
import {DebouncedEventEmitter} from 'core-components/angular/debounced-event-emitter';
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
import {TimezoneService} from 'core-components/datetime/timezone.service';
@Component({
selector: 'filter-date-times-value',
@ -47,8 +47,8 @@ export class FilterDateTimesValueComponent extends AbstractDateTimeValueControll
};
constructor(@Inject(I18nToken) readonly I18n:op.I18n,
@Inject(TimezoneServiceToken) readonly TimezoneService:any) {
super(I18n, TimezoneService);
readonly timezoneService:TimezoneService) {
super(I18n, timezoneService);
}
ngOnDestroy() {
@ -74,16 +74,16 @@ export class FilterDateTimesValueComponent extends AbstractDateTimeValueControll
}
public get lowerBoundary() {
if (this.begin && this.TimezoneService.isValidISODateTime(this.begin)) {
return this.TimezoneService.parseDatetime(this.begin);
if (this.begin && this.timezoneService.isValidISODateTime(this.begin.toString())) {
return this.timezoneService.parseDatetime(this.begin.toString());
} else {
return null;
}
}
public get upperBoundary() {
if (this.end && this.TimezoneService.isValidISODateTime(this.end)) {
return this.TimezoneService.parseDatetime(this.end);
if (this.end && this.timezoneService.isValidISODateTime(this.end.toString())) {
return this.timezoneService.parseDatetime(this.end.toString());
} else {
return null;
}

@ -27,10 +27,11 @@
//++
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
import {I18nToken, TimezoneServiceToken} from 'core-app/angular4-transition-utils';
import {Component, EventEmitter, Inject, Input, OnDestroy, Output} from '@angular/core';
import {I18nToken} from 'core-app/angular4-transition-utils';
import {Component, Inject, Input, OnDestroy, Output} from '@angular/core';
import {DebouncedEventEmitter} from 'core-components/angular/debounced-event-emitter';
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
import {TimezoneService} from 'core-components/datetime/timezone.service';
@Component({
selector: 'filter-date-value',
@ -40,7 +41,7 @@ export class FilterDateValueComponent implements OnDestroy {
@Input() public filter:QueryFilterInstanceResource;
@Output() public filterChanged = new DebouncedEventEmitter<QueryFilterInstanceResource>(componentDestroyed(this));
constructor(@Inject(TimezoneServiceToken) readonly TimezoneService:any,
constructor(readonly timezoneService:TimezoneService,
@Inject(I18nToken) readonly I18n:op.I18n) {
}
@ -67,8 +68,8 @@ export class FilterDateValueComponent implements OnDestroy {
public formatter(data:any) {
if (moment(data, 'YYYY-MM-DD', true).isValid()) {
var d = this.TimezoneService.parseDate(data);
return this.TimezoneService.formattedISODate(d);
var d = this.timezoneService.parseDate(data);
return this.timezoneService.formattedISODate(d);
} else {
return null;
}

@ -28,9 +28,10 @@
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
import {Component, Inject, Input, OnDestroy, Output} from '@angular/core';
import {I18nToken, TimezoneServiceToken} from 'core-app/angular4-transition-utils';
import {I18nToken} from 'core-app/angular4-transition-utils';
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
import {DebouncedEventEmitter} from 'core-components/angular/debounced-event-emitter';
import {TimezoneService} from 'core-components/datetime/timezone.service';
@Component({
selector: 'filter-dates-value',
@ -44,7 +45,7 @@ export class FilterDatesValueComponent implements OnDestroy {
spacer: this.I18n.t('js.filter.value_spacer')
};
constructor(@Inject(TimezoneServiceToken) readonly TimezoneService:any,
constructor(readonly timezoneService:TimezoneService,
@Inject(I18nToken) readonly I18n:op.I18n) {
}
@ -80,8 +81,8 @@ export class FilterDatesValueComponent implements OnDestroy {
public formatter(data:any) {
if (moment(data, 'YYYY-MM-DD', true).isValid()) {
var d = this.TimezoneService.parseDate(data);
return this.TimezoneService.formattedISODate(d);
var d = this.timezoneService.parseDate(data);
return this.timezoneService.formattedISODate(d);
} else {
return null;
}

@ -1,56 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
function transformDateUtc(TimezoneService:any):any {
return {
restrict: 'A',
scope: {
},
require: '^ngModel',
link: function (scope:ng.IScope, element:HTMLElement, attrs:any, ngModelController:any) {
ngModelController.$parsers.push(function (data:string) {
if (!TimezoneService.isValidISODate(data)) {
return '';
}
var d = TimezoneService.parseLocalDateTime(data);
return TimezoneService.formattedISODateTime(d);
});
ngModelController.$formatters.push(function (data:string) {
if (!TimezoneService.isValidISODateTime(data)) {
return '';
}
var d = TimezoneService.parseISODatetime(data);
return TimezoneService.formattedISODate(d);
});
}
};
}
angular
.module('openproject')
.directive('transformDateUtc', transformDateUtc);

@ -1,64 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
function transformDate(TimezoneService:any) {
return {
restrict:'A',
require: '^ngModel',
link: function(
scope:ng.IScope,
element:ng.IAugmentedJQuery,
attrs:ng.IAttributes,
ngModelController:any) {
ngModelController.$parsers.push(function(data:any) {
if (moment(data, 'YYYY-MM-DD', true).isValid()) {
return data;
} else {
return null;
}
});
ngModelController.$formatters.push(function(data:any) {
if (moment(data, 'YYYY-MM-DD', true).isValid()) {
var d = TimezoneService.parseDate(data);
return TimezoneService.formattedISODate(d);
} else {
return null;
}
});
}
};
}
// TODO:deprecate and replace by transformDate
angular
.module('openproject')
.directive('transformDateValue', transformDate);
// angular
// .module('openproject')
// .directive('transformDate', transformDate);

@ -1,56 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
import {Duration} from 'moment';
function transformDuration(TimezoneService:any) {
return {
restrict:'A',
require: 'ngModel',
link: function(
scope:ng.IScope,
element:ng.IAugmentedJQuery,
attrs:ng.IAttributes,
ngModelController:any) {
ngModelController.$parsers.push(function(value:any):Duration|void {
if (!isNaN(value)) {
let floatValue = parseFloat(value);
return moment.duration(floatValue, 'hours');
}
});
ngModelController.$formatters.push(function(value:any) {
return TimezoneService.toHours(value);
});
}
};
};
angular
.module('openproject')
.directive('transformDurationValue', transformDuration);

@ -1,64 +0,0 @@
// -- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
// ++
var expect = chai.expect;
describe('requestForConfirmation directive', () => {
var scope:any, element:any, controller:any, body:any;
beforeEach(angular.mock.module('openproject', 'openproject.uiComponents'));
beforeEach(angular.mock.inject(($compile:any, $rootScope:any) => {
var html = `
<form class="request-for-confirmation">
<input type="text" id="somevalue" />
</form>
`;
element = angular.element(html);
scope = $rootScope.$new();
body = angular.element(document.body);
$compile(element)(scope);
body.append(element);
scope.$digest();
controller = element.controller('requestForConfirmation');
}));
it('should call the dialog when submitting', () => {
sinon.spy(controller, 'openConfirmationDialog');
element.trigger('submit');
expect(controller.openConfirmationDialog).to.have.been.called;
});
afterEach(() => {
element.remove();
body.remove('.ngdialog-theme-openproject');
});
});

@ -1,137 +0,0 @@
// -- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
// ++
/*jshint expr: true*/
describe('workPackageCommentDirectiveTest', function() {
var I18n, wpActivityService, compile, scope, element, stateParams, q, commentCreation;
var workPackageFieldService = {};
var html = "<work-package-comment work-package='workPackage' activities='activities'></work-package-comment>";
stateParams = {};
beforeEach(angular.mock.module('ui.router',
'openproject',
'openproject.api',
'openproject.models',
'openproject.layout',
'openproject.services',
'openproject.uiComponents',
'openproject.inplace-edit',
'openproject.workPackages.tabs',
'openproject.workPackages.directives',
'openproject.workPackages.models'));
beforeEach(angular.mock.module('openproject.templates', function($provide) {
var configurationService = {
commentsSortedInDescendingOrder: function() { return []; }
};
$provide.constant('$stateParams', stateParams);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(angular.mock.module('openproject.services', function($provide) {
$provide.constant('WorkPackageFieldService', workPackageFieldService);
$provide.constant('WorkPackageService', {});
}));
beforeEach(inject(function($rootScope, $compile, $q, _I18n_, _wpActivityService_) {
I18n = _I18n_;
q = $q;
scope = $rootScope.$new();
compile = function() {
workPackageFieldService.isEmpty = sinon.stub().returns(true);
element = $compile(html)(scope);
scope.$digest();
};
workPackageFieldService.isEmpty = sinon.stub().returns(true);
wpActivityService = _wpActivityService_;
var createComments = sinon.stub(wpActivityService, 'createComment');
commentCreation = q.defer();
createComments.returns(commentCreation.promise);
var stub = sinon.stub(I18n, 't');
stub.withArgs('js.label_add_comment_title').returns('trans_title');
stub.withArgs('js.label_add_comment').returns('trans_add_comment');
stub.withArgs('js.button_cancel').returns('trans_cancel');
}));
afterEach(function() {
I18n.t.restore();
wpActivityService.createComment.restore();
});
beforeEach(function() {
var workPackage = {
addComment: {
$link: {
href: 'addComment'
}
},
$plain: function() { return {}; }
};
scope.workPackage = workPackage;
});
describe('activity comments', function() {
describe('without comment link in work package', function() {
beforeEach(function() {
scope.workPackage.addComment = undefined;
compile();
});
it('should not display the comments form', function() {
expect(element.find('.work-packages--activity--add-comment').length).to.equal(0);
});
});
describe('with comment link in work package', function() {
var commentSection, commentField;
beforeEach(function() {
compile();
commentSection = element.find('.work-packages--activity--add-comment');
});
it('should display the comments form', function() {
expect(commentSection.length).to.equal(1);
});
it('should display a placeholder in the comments field', function() {
var readvalue = commentSection.find('.inplace-edit--read-value > span');
expect(readvalue.text().trim()).to.equal('trans_title');
});
});
});
});

@ -1,71 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
/*jshint expr: true*/
describe('WorkPackageService', function() {
var WorkPackageService,
stateParams = {};
beforeEach(angular.mock.module('openproject.api', 'openproject.layout','openproject.services',
'openproject.models'));
beforeEach(angular.mock.module('openproject.templates', function($provide) {
var configurationService = {};
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('$stateParams', stateParams);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(inject(function(_WorkPackageService_){
WorkPackageService = _WorkPackageService_;
}));
describe('performBulkDelete', function() {
var deleteFunction;
beforeEach(inject(function($http) {
deleteFunction = sinon.stub($http, 'delete');
}));
beforeEach(inject(function($http) {
var ids = [1,2]
WorkPackageService.performBulkDelete(ids);
}));
it('sends a delete request', function() {
expect(deleteFunction).to.have.been.called;
});
it('sends the work package ids to the bulk delete action', function() {
expect(deleteFunction).to.have.been.calledWith('/work_packages/bulk', { params: { 'ids[]': [1, 2] } });
});
});
});

@ -1,130 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
import {wpDirectivesModule, opTemplatesModule, opConfigModule} from '../../../angular-modules';
import {WorkPackageUploadDirectiveController} from './wp-attachments-upload.directive';
import ICompileService = angular.ICompileService;
import IRootScopeService = angular.IRootScopeService;
import IAugmentedJQuery = angular.IAugmentedJQuery;
import IQService = angular.IQService;
import ICompileProvider = angular.ICompileProvider;
describe('wpAttachmentsUpload directive', () => {
var $rootScope: IRootScopeService;
var $q: IQService;
var html: string;
var compile: any;
var element: IAugmentedJQuery;
var controller: WorkPackageUploadDirectiveController;
var rootElement: IAugmentedJQuery;
var workPackage: any;
var mockMaxSize: number = 123;
beforeEach(angular.mock.module(
wpDirectivesModule.name,
opConfigModule.name,
opTemplatesModule.name,
($compileProvider: ICompileProvider) => {
$compileProvider.directive('ngfDrop', () => ({
restrict: 'A',
scope: {ngfChange: '&', ngModel: '='},
controller: () => angular.noop(),
controllerAs: '$ctrl',
bindToController: true
}));
}));
beforeEach(angular.mock.inject(function (_$rootScope_: IRootScopeService,
_$q_:any,
$compile: ICompileService,
ConfigurationService:any) {
[$rootScope, $q] = _.toArray(arguments);
html = `<wp-attachments-upload attachments="attachments" work-package="workPackage">
</wp-attachments-upload>`;
workPackage = {
canAddAttachments: false,
attachments: {pending: []}
};
const scope: any = $rootScope.$new();
scope.workPackage = workPackage;
ConfigurationService.api = () => $q.when({maximumAttachmentFileSize: mockMaxSize});
compile = () => {
element = $compile(html)(scope);
scope.$digest();
controller = element.controller('wpAttachmentsUpload');
rootElement = element.find('.wp-attachment-upload');
};
compile();
}));
it('should not be empty', () => {
expect(element.html()).to.not.be.empty;
});
it('should not be rendered', () => {
expect(rootElement).to.have.length(0);
});
it('should have the provided maxFileSize', () => {
expect(controller.maxFileSize).to.eq(mockMaxSize);
});
describe('when it is possible to add attachments to the work package', () => {
beforeEach(() => {
workPackage.canAddAttachments = true;
compile();
});
it('should display the directive', () => {
expect(rootElement).to.have.length(1);
});
describe('when clicking the parent element', () => {
var clicked:any;
beforeEach(() => {
clicked = false;
rootElement.click(() => clicked = true);
element.click();
});
it('should click the first child', () => {
expect(clicked).to.be.true;
});
});
});
});

@ -27,15 +27,15 @@
// ++
import {DisplayField} from "../wp-display-field/wp-display-field.module";
import {TimezoneServiceToken} from 'core-app/angular4-transition-utils';
import {TimezoneService} from 'core-components/datetime/timezone.service';
export class DateDisplayField extends DisplayField {
private TimezoneService = this.$injector.get(TimezoneServiceToken);
private timezoneService = this.$injector.get(TimezoneService);
public get valueString() {
if (this.value) {
return this.TimezoneService.formattedDate(this.value);
return this.timezoneService.formattedDate(this.value);
} else {
return '';
}

@ -27,14 +27,14 @@
// ++
import {DisplayField} from "../wp-display-field/wp-display-field.module";
import {TimezoneServiceToken} from 'core-app/angular4-transition-utils';
import {TimezoneService} from 'core-components/datetime/timezone.service';
export class DateTimeDisplayField extends DisplayField {
private TimezoneService = this.$injector.get(TimezoneServiceToken);
private timezoneService = this.$injector.get(TimezoneService);
public get valueString() {
if (this.value) {
return this.TimezoneService.formattedDatetime(this.value);
return this.timezoneService.formattedDatetime(this.value);
}
return '';

@ -27,26 +27,26 @@
// ++
import {DisplayField} from "../wp-display-field/wp-display-field.module";
import {TimezoneServiceToken} from 'core-app/angular4-transition-utils';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {TimezoneService} from 'core-components/datetime/timezone.service';
export class DurationDisplayField extends DisplayField {
private TimezoneService:any;
private timezoneService:TimezoneService;
constructor(public resource:HalResource,
public name:string,
public schema:op.FieldSchema) {
super(resource, name, schema);
this.TimezoneService = this.$injector.get(TimezoneServiceToken);
this.timezoneService = this.$injector.get(TimezoneService);
}
public get valueString() {
return this.TimezoneService.formattedDuration(this.value);
return this.timezoneService.formattedDuration(this.value);
}
public isEmpty():boolean {
return this.TimezoneService.toHours(this.value) === 0;
return this.timezoneService.toHours(this.value) === 0;
}
}

@ -27,6 +27,7 @@
//++
import {ConfigurationService} from '../../common/config/configuration.service';
import {TimezoneService} from 'core-components/datetime/timezone.service';
export class DatePicker {
public datepickerFormat = 'yy-mm-dd';
@ -35,7 +36,7 @@ export class DatePicker {
private datepickerInstance:any = null;
constructor(readonly ConfigurationService:ConfigurationService,
readonly TimezoneService:any,
readonly timezoneService:TimezoneService,
private datepickerElem:JQuery,
private date:any,
private options:any) {
@ -52,7 +53,7 @@ export class DatePicker {
changeMonth: true,
changeYear: true,
dateFormat: this.datepickerFormat,
defaultDate: this.TimezoneService.formattedISODate(this.date),
defaultDate: this.timezoneService.formattedISODate(this.date),
showButtonPanel: true
});

@ -28,8 +28,8 @@
import {Component, ElementRef, EventEmitter, Inject, Input, OnInit, Output} from '@angular/core';
import {ConfigurationService} from 'core-components/common/config/configuration.service';
import {TimezoneServiceToken} from 'core-app/angular4-transition-utils';
import {DatePicker} from 'core-components/wp-edit/op-date-picker/datepicker';
import {TimezoneService} from 'core-components/datetime/timezone.service';
@Component({
selector: 'op-date-picker',
@ -38,7 +38,7 @@ import {DatePicker} from 'core-components/wp-edit/op-date-picker/datepicker';
export class OpDatePickerComponent implements OnInit {
@Output() public onChange = new EventEmitter<string>();
@Output() public onClose = new EventEmitter<string>();
@Input() public initialDate?:String;
@Input() public initialDate?:string;
private $element:JQuery;
private datePickerInstance:any;
@ -46,7 +46,7 @@ export class OpDatePickerComponent implements OnInit {
public constructor(private elementRef:ElementRef,
private ConfigurationService:ConfigurationService,
@Inject(TimezoneServiceToken)private TimezoneService:any) {
private timezoneService:TimezoneService) {
}
@ -103,14 +103,14 @@ export class OpDatePickerComponent implements OnInit {
let initialValue;
if (this.isEmpty && this.initialDate) {
initialValue = this.TimezoneService.parseISODate(this.initialDate).toDate();
initialValue = this.timezoneService.parseISODate(this.initialDate).toDate();
} else {
initialValue = this.currentValue();
}
this.datePickerInstance = new DatePicker(
this.ConfigurationService,
this.TimezoneService,
this.timezoneService,
this.input,
initialValue,
options

@ -30,18 +30,19 @@
// Remove when this is no longer the case and migrate to the ng2 component instead.
import {DatePicker} from 'core-components/wp-edit/op-date-picker/datepicker';
import {TimezoneService} from 'core-components/datetime/timezone.service';
class OPDatePickerController {
public onChange?:Function;
public onClose?:Function;
public initialDate?:String;
public initialDate?:string;
private datePickerInstance:any;
private input:JQuery;
public constructor(private $element:ng.IAugmentedJQuery,
private ConfigurationService:any,
private TimezoneService:any) {
private timezoneService:TimezoneService) {
'ngInject';
}
@ -101,14 +102,14 @@ class OPDatePickerController {
let initialValue;
if (this.isEmpty && this.initialDate) {
initialValue = this.TimezoneService.parseISODate(this.initialDate).toDate();
initialValue = this.timezoneService.parseISODate(this.initialDate).toDate();
} else {
initialValue = this.currentValue();
}
this.datePickerInstance = new DatePicker(
this.ConfigurationService,
this.TimezoneService,
this.timezoneService,
this.input,
initialValue,
options

@ -0,0 +1,171 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
/*jshint expr: true*/
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {QueryMenuService} from 'core-components/wp-query-menu/wp-query-menu.service';
import {WpQueryMenuDirective} from 'core-components/wp-query-menu/wp-query-menu.directive';
import {Component} from '@angular/core';
import {WorkPackagesListChecksumService} from 'core-components/wp-list/wp-list-checksum.service';
import {TransitionService} from '@uirouter/core';
import {$stateToken} from 'core-app/angular4-transition-utils';
@Component({
template: `
<div id="main-menu-work-packages-wrapper">
<a id="main-menu-work-packages" wp-query-menu>Work packages</a>
<ul class="menu-children"></ul>'
</div>
`
})
class WpQueryMenuTestComponent { }
describe('wp-query-menu directive', () => {
let app:WpQueryMenuTestComponent;
let fixture:ComponentFixture<WpQueryMenuTestComponent>
let element:JQuery;
let menuContainer:JQuery;
let queryMenuService = new QueryMenuService();
let transitionCallback:(id:any) => any;
const $transitionStub = {
onStart: (criteria:any, callback:(transition:any) => any) => {
transitionCallback = (id:any) => callback({
params: (val:string) => { return { queryId: id }; }
} as any);
}
};
beforeEach(async(() => {
// noinspection JSIgnoredPromiseFromCall
return TestBed.configureTestingModule({
declarations: [
WpQueryMenuTestComponent,
WpQueryMenuDirective
],
providers: [
{ provide: $stateToken, useValue: { go: (...args:any[]) => undefined } },
{ provide: WorkPackagesListChecksumService, useValue: { clear: () => undefined } },
{ provide: TransitionService, useValue: $transitionStub },
{ provide: QueryMenuService, useValue: queryMenuService },
]
}).compileComponents()
.then(() => {
fixture = TestBed.createComponent(WpQueryMenuTestComponent);
app = fixture.debugElement.componentInstance;
element = jQuery(fixture.elementRef.nativeElement);
menuContainer = element.find('ul.menu-children');
});
}));
describe('#add for a query', function() {
var menuItem:any, itemLink:any;
var path = '/work_packages?query_id=1',
title = 'Query',
objectId = '1';
var generateMenuItem = function() {
queryMenuService.add(title, path, objectId);
fixture.detectChanges();
menuItem = menuContainer.children('li');
itemLink = menuItem.children('a');
};
beforeEach(() => {
generateMenuItem();
});
it ('adds a query menu item', function() {
expect(menuItem).to.have.length(1);
});
it('assigns the item type as class', function() {
expect(itemLink.hasClass('query-menu-item')).to.be.true;
});
describe('when the query id matches the query id of the state params', function() {
beforeEach(inject(function() {
transitionCallback(objectId);
fixture.detectChanges();
}));
it('marks the new item as selected', function() {
expect(itemLink.hasClass('selected'), 'is selected').to.be.true;
});
it('toggles the selected state on state change', function() {
transitionCallback(null);
fixture.detectChanges();
expect(itemLink.hasClass('selected'), 'is selected').to.be.false;
});
});
});
describe('#generateMenuItem for the work package index item', function() {
var menuItem:any, itemLink:any;
var path = '/work_packages',
title = 'Work Packages',
objectId:any = undefined;
beforeEach(function() {
queryMenuService.add(title, path, objectId);
fixture.detectChanges();
menuItem = menuContainer.children('li');
itemLink = menuItem.children('a');
});
describe('on a work_package page', function() {
describe('for a null query_id', function() {
beforeEach(inject(function() {
transitionCallback(null);
fixture.detectChanges();
}));
it('marks the item as selected', function() {
expect(itemLink.hasClass('selected'), 'is not selected').to.be.false;
});
});
describe('for a string query_id', function() {
beforeEach(inject(function() {
transitionCallback('1');
fixture.detectChanges();
}));
it('does not mark the item as selected', function() {
expect(itemLink.hasClass('selected'), 'is not selected').to.be.false;
});
});
});
});
});

@ -1,244 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
/*jshint expr: true*/
import {QueryMenuService} from 'core-components/wp-query-menu/wp-query-menu.service';
describe('queryMenuItemFactory', function() {
var menuContainer:any, element:any, document:any,
$rootScope:ng.IRootScopeService, scope:ng.IScope,
queryMenu:QueryMenuService, state:any = {}, stateParams:any = {};
beforeEach(angular.mock.module('openproject.layout'));
beforeEach(angular.mock.module('openproject.templates',
'openproject.services',
'openproject.uiComponents',
'openproject.models',
'openproject.api',
function($provide:ng.auto.IProvideService) {
var configurationService:any = {};
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(angular.mock.module('openproject.templates', function($provide:ng.auto.IProvideService) {
// Mock check whether we are on a work_packages page
state = { includes: function() { return true; } };
$provide.value('$state', state);
$provide.value('$stateParams', stateParams);
}));
beforeEach(inject(function(_$rootScope_:ng.IRootScopeService,
$compile:ng.ICompileService,
$document:ng.IDocumentService) {
$rootScope = _$rootScope_;
// set up html body
const template = `
<div id="main-menu-work-packages-wrapper">
<a id="main-menu-work-packages" wp-query-menu>Work packages</a>
<ul class="menu-children"></ul>'
</div>
`;
element = angular.element(template);
$compile(element)($rootScope);
document = $document[0];
var body = angular.element(document.body);
body.append(element);
menuContainer = element.find('ul.menu-children');
}));
beforeEach(inject(function(_queryMenu_:QueryMenuService) {
queryMenu = _queryMenu_;
}));
afterEach(inject(function() {
// The document does not seem to be cleaned up after each test instead each
// test leaves additional DOM. Thus the tests are not independent.
// Therefore we clean it by hand.
element.remove();
}));
describe('#add for a query', function() {
var menuItem:any, itemLink:any;
var path = '/work_packages?query_id=1',
title = 'Query',
objectId = '1';
var generateMenuItem = function() {
queryMenu.add(title, path, objectId);
menuItem = menuContainer.children('li');
itemLink = menuItem.children('a');
scope = itemLink.scope();
};
beforeEach(generateMenuItem);
it ('adds a query menu item', function() {
expect(menuItem).to.have.length(1);
});
it('assigns the item type as class', function() {
expect(itemLink.hasClass('query-menu-item')).to.be.true;
});
describe('when the query id matches the query id of the state params', function() {
beforeEach(inject(function() {
stateParams['query_id'] = objectId;
$rootScope.$apply();
}));
it('marks the new item as selected', function() {
expect(itemLink.hasClass('selected')).to.be.true;
});
it('toggles the selected state on state change', function() {
stateParams['query_id'] = null;
$rootScope.$apply();
expect(itemLink.hasClass('selected')).to.be.false;
});
});
describe('when the query id is undefined', function(){
beforeEach(inject(function() {
stateParams['query_id'] = undefined;
$rootScope.$apply();
}));
it('marks the new item as unselected', function() {
expect(itemLink.hasClass('selected')).to.be.false;
});
it('toggles the selected state on state change', function() {
stateParams['query_id'] = objectId;
$rootScope.$apply();
expect(itemLink.hasClass('selected')).to.be.true;
});
});
});
describe('#generateMenuItem for the work package index item', function() {
var menuItem:any, itemLink:any;
var path = '/work_packages',
title = 'Work Packages',
objectId:any = undefined;
beforeEach(function() {
queryMenu.add(title, path, objectId);
$rootScope.$apply();
menuItem = menuContainer.children('li');
itemLink = menuItem.children('a');
scope = itemLink.scope();
});
describe('on a work_package page', function() {
describe('for an undefined query_id', function() {
beforeEach(inject(function() {
stateParams['query_id'] = undefined;
$rootScope.$apply();
}));
it('marks the item as selected', function() {
expect(itemLink.hasClass('selected')).to.be.false;
});
});
describe('for a null query_id', function() {
beforeEach(inject(function() {
stateParams['query_id'] = null;
$rootScope.$apply();
}));
it('marks the item as selected', function() {
expect(itemLink.hasClass('selected')).to.be.false;
});
});
describe('for an integer query_id', function() {
beforeEach(inject(function() {
stateParams['query_id'] = 1;
$rootScope.$apply();
}));
it('does not mark the item as selected', function() {
expect(itemLink.hasClass('selected')).to.be.false;
});
});
describe('for a string query_id', function() {
beforeEach(inject(function() {
stateParams['query_id'] = "1";
$rootScope.$apply();
}));
it('does not mark the item as selected', function() {
expect(itemLink.hasClass('selected')).to.be.false;
});
});
});
describe('on a non-work package page', function() {
beforeEach(function() {
// Change mock for checking whether we are on a work_packages page
state.includes = function() { return false; };
});
describe('for an undefined query_id', function() {
beforeEach(inject(function() {
stateParams['query_id'] = undefined;
$rootScope.$apply();
}));
it('marks the item as selected', function() {
expect(itemLink.hasClass('selected')).to.be.false;
});
});
describe('for a null query_id', function() {
beforeEach(inject(function() {
stateParams['query_id'] = null;
$rootScope.$apply();
}));
it('marks the item as selected', function() {
expect(itemLink.hasClass('selected')).to.be.false;
});
});
});
});
});

@ -33,7 +33,7 @@ import {
QueryMenuService
} from 'core-components/wp-query-menu/wp-query-menu.service';
import {StateService, Transition, TransitionService} from '@uirouter/core';
import {Directive, ElementRef, Inject} from '@angular/core';
import {Directive, ElementRef, Inject, OnInit} from '@angular/core';
import {$stateToken} from 'core-app/angular4-transition-utils';
export const QUERY_MENU_ITEM_TYPE = 'query-menu-item';
@ -41,8 +41,8 @@ export const QUERY_MENU_ITEM_TYPE = 'query-menu-item';
@Directive({
selector: '[wp-query-menu]'
})
export class WpQueryMenuDirective {
private currentQueryId?:string;
export class WpQueryMenuDirective implements OnInit {
private currentQueryId:string|null = null;
private uiRouteStateName = 'work-packages.list';
private container:ng.IAugmentedJQuery;
@ -55,17 +55,16 @@ export class WpQueryMenuDirective {
protected wpListChecksumService:WorkPackagesListChecksumService) {
}
public $onInit() {
public ngOnInit() {
this.$element = jQuery(this.elementRef.nativeElement);
this.container = this.$element.parent().find('ul.menu-children');
this.$transitions.onStart({}, (transition:Transition) => {
const queryId = transition.params('to').queryId;
this.currentQueryId = queryId;
this.setSelectedState();
this.onQueryIdChanged(queryId);
});
this.queryMenu
this.queryMenu
.on('remove')
.subscribe((e:QueryMenuEvent) => this.removeItem(e));
@ -88,6 +87,11 @@ export class WpQueryMenuDirective {
});
}
public onQueryIdChanged(queryId:string|null) {
this.currentQueryId = queryId;
this.setSelectedState();
}
private removeItem(e:QueryMenuEvent) {
const item = this.findItem(e.queryId);
this.setSelectedState();

@ -26,11 +26,11 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {DatePipe} from '@angular/common';
import {TimezoneService} from 'core-components/datetime/timezone.service';
export class ActivityEntryInfo {
constructor(public timezoneService:any,
constructor(public timezoneService:TimezoneService,
public isReversed:boolean,
public activities:any[],
public activity:any,

@ -31,14 +31,14 @@ import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-r
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {Inject, Injectable} from '@angular/core';
import {ConfigurationService} from 'core-components/common/config/configuration.service';
import {TimezoneServiceToken} from 'core-app/angular4-transition-utils';
import {WorkPackageLinkedResourceCache} from 'core-components/wp-single-view-tabs/wp-linked-resource-cache.service';
import {TimezoneService} from 'core-components/datetime/timezone.service';
@Injectable()
export class WorkPackagesActivityService extends WorkPackageLinkedResourceCache<HalResource[]> {
constructor(public ConfigurationService:ConfigurationService,
@Inject(TimezoneServiceToken) readonly timezoneService:any) {
readonly timezoneService:TimezoneService) {
super();
}

@ -31,7 +31,7 @@ import {WorkPackageTableTimelineService} from '../../../wp-fast-table/state/wp-t
import {DisplayFieldRenderer} from '../../../wp-edit-form/display-field-renderer';
import Moment = moment.Moment;
import {Injector} from '@angular/core';
import {TimezoneServiceToken} from 'core-app/angular4-transition-utils';
import {TimezoneService} from 'core-components/datetime/timezone.service';
export interface CellDateMovement {
// Target values to move work package to
@ -48,7 +48,7 @@ function calculateForegroundColor(backgroundColor:string):string {
}
export class TimelineCellRenderer {
readonly TimezoneService = this.injector.get(TimezoneServiceToken);
readonly TimezoneService = this.injector.get(TimezoneService);
readonly wpTableTimeline:WorkPackageTableTimelineService = this.injector.get(WorkPackageTableTimelineService);
public fieldRenderer:DisplayFieldRenderer = new DisplayFieldRenderer(this.injector, 'timeline');

@ -57,6 +57,7 @@ catch(e) {
import {openprojectModule} from './angular-modules';
import {whenDebugging} from 'core-app/helpers/debug_output';
import {enableReactiveStatesLogging} from 'reactivestates';
import {TimezoneService} from 'core-components/datetime/timezone.service';
window.appBasePath = jQuery('meta[name=app_base_path]').attr('content') || '';
@ -117,7 +118,7 @@ openprojectModule
function($http:ng.IHttpService,
$rootScope:any,
$window:ng.IWindowService,
TimezoneService:any,
timezoneService:TimezoneService,
ExpressionService:ExpressionService,
KeyboardShortcutService:any) {
@ -138,7 +139,7 @@ openprojectModule
'collapsed';
}
TimezoneService.setupLocale();
timezoneService.setupLocale();
KeyboardShortcutService.activate();
angular.element('body').addClass('__ng-bootstrap-has-run');

@ -36,6 +36,4 @@ angular.module('openproject.services')
require('./keyboard-shortcut-service')])
.service('SortService', require('./sort-service'))
.service('StatusService', ['$http', 'PathHelper', require('./status-service')])
.service('TimezoneService', ['ConfigurationService', 'I18n', require(
'./timezone-service')])
.service('ConversionService', require('./conversion-service.js'));

@ -1,157 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function(ConfigurationService, I18n) {
var TimezoneService = {
setupLocale: function() {
moment.locale(I18n.locale);
},
/**
* Takes a utc date time string and turns it into
* a local date time moment object.
*/
parseDatetime: function(datetime, format) {
var d = moment.utc(datetime, format);
if (ConfigurationService.isTimezoneSet()) {
d.local();
d.tz(ConfigurationService.timezone());
}
return d;
},
parseDate: function(date, format) {
return moment(date, format);
},
/**
* Parses a string that is considered to be a local date and
* turns it into a utc date time moment object.
* 'Local' might mean the browsers default time zone or the one configured
* in the Configuration Service.
*
* @param {String} date
* @param {String} format
* @returns {Moment}
*/
parseLocalDateTime: function(date, format) {
var result;
if (ConfigurationService.isTimezoneSet()) {
result = moment.tz(date, format, ConfigurationService.timezone());
} else {
result = moment(date, format);
}
result.utc();
return result;
},
/**
* Parses the specified datetime and applies the user's configured timezone, if any.
*
* This will effectfully transform the [server] provided datetime object to the user's configured local timezone.
*
* @param {String} datetime in 'YYYY-MM-DDTHH:mm:ssZ' format
* @returns {Moment}
*/
parseISODatetime: function(datetime) {
return this.parseDatetime(datetime, 'YYYY-MM-DDTHH:mm:ssZ');
},
parseISODate: function(date) {
return TimezoneService.parseDate(date, 'YYYY-MM-DD');
},
formattedDate: function(date) {
var d = TimezoneService.parseDate(date);
return d.format(TimezoneService.getDateFormat());
},
formattedTime: function(datetimeString) {
return TimezoneService.parseDatetime(datetimeString).format(TimezoneService.getTimeFormat());
},
formattedDatetime: function(datetimeString) {
var c = TimezoneService.formattedDatetimeComponents(datetimeString);
return c[0] + ' ' + c[1];
},
formattedDatetimeComponents: function(datetimeString) {
var d = TimezoneService.parseDatetime(datetimeString);
return [
d.format(TimezoneService.getDateFormat()),
d.format(TimezoneService.getTimeFormat())
];
},
toHours: function(durationString) {
return Number(moment.duration(durationString).asHours().toFixed(2));
},
formattedDuration: function(durationString) {
return I18n.t('js.units.hour', { count: TimezoneService.toHours(durationString) });
},
formattedISODate: function(date) {
return TimezoneService.parseDate(date).format('YYYY-MM-DD');
},
formattedISODateTime: function(datetime) {
return datetime.format();
},
isValidISODate: function(date) {
return TimezoneService.isValid(date, 'YYYY-MM-DD');
},
isValidISODateTime: function(dateTime) {
return TimezoneService.isValid(dateTime, 'YYYY-MM-DDTHH:mm:ssZ');
},
isValid: function(date, dateFormat) {
var format = dateFormat || (ConfigurationService.dateFormatPresent() ?
ConfigurationService.dateFormat() : 'L');
return moment(date, [format], true).isValid();
},
getDateFormat: function() {
return ConfigurationService.dateFormatPresent() ? ConfigurationService.dateFormat() : 'L';
},
getTimeFormat: function() {
return ConfigurationService.timeFormatPresent() ? ConfigurationService.timeFormat() : 'LT';
}
};
return TimezoneService;
};

@ -139,6 +139,14 @@
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz",
"integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw=="
},
"@types/moment-timezone": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@types/moment-timezone/-/moment-timezone-0.5.4.tgz",
"integrity": "sha512-GXWxTNJaN6A6CSa7+8a0ZGN5yGozW3So7fFHrXE/1c976Nx4hv2470Bx/q6/nmNNOYbcuKT61P0kRNf4cQTirw==",
"requires": {
"moment": "2.20.1"
}
},
"@types/mousetrap": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@types/mousetrap/-/mousetrap-1.6.0.tgz",
@ -3572,7 +3580,7 @@
"jquery-mockjax": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/jquery-mockjax/-/jquery-mockjax-2.2.2.tgz",
"integrity": "sha1-nH0rEDpEz2HJnMeKnueHhQS2CQc=",
"integrity": "sha512-stVgUln5bo7g14KjeU45c31nAYlBHAUVBo4JbdoDYIJ3i6Nzlu8mkjKcAviwad9AU1dBEHHpuzA7R6mKcWgvZA==",
"dev": true,
"requires": {
"jquery": "3.3.1"

@ -47,6 +47,7 @@
"@types/jqueryui": "^1.11.32",
"@types/lodash": "^4.14.100",
"@types/mocha": "^2.2.39",
"@types/moment-timezone": "^0.5.4",
"@types/mousetrap": "^1.5.33",
"@types/ng-dialog": "^0.6.0",
"@types/promises-a-plus": "0.0.27",

@ -1,80 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
describe('transformDateValue Directive', function() {
var compile, element, rootScope, scope;
var shouldBehaveLikeAParser = function (value, expected) {
it('these should parse as expected', function () {
compile();
element.val(value);
element.trigger('input');
expect(scope.dateValue).to.eql(expected);
});
};
var shouldBehaveLikeAFormatter = function (value, expected) {
it('should format the value as expected', function () {
scope.dateValue = value;
compile();
expect(element.val()).to.eql(expected);
});
}
beforeEach(angular.mock.module('openproject'));
beforeEach(inject(function($rootScope, $compile) {
var html =
'<input type="text" ng-model="dateValue" transform-date-value/>';
element = angular.element(html);
rootScope = $rootScope;
scope = $rootScope.$new();
scope.dateValue = null;
compile = function() {
$compile(element)(scope);
scope.$digest();
};
}));
describe('when given valid date values', function() {
var dateValue = '2016-12-01';
shouldBehaveLikeAParser(dateValue, dateValue);
shouldBehaveLikeAFormatter(dateValue, dateValue);
});
describe('when given invalid date values', function () {
shouldBehaveLikeAParser('', null);
shouldBehaveLikeAParser('invalid', null);
shouldBehaveLikeAParser('2016-12', null);
shouldBehaveLikeAFormatter(undefined, '');
shouldBehaveLikeAFormatter(null, '');
shouldBehaveLikeAFormatter('2016-12', '');
});
});

@ -1,128 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
describe('transformDateUtc Directive', function() {
var compile, element, rootScope, scope, ConfigurationService, isTimezoneSetStub, timezoneStub;
var prepare = function () {
angular.mock.module('openproject');
inject(function($rootScope, $compile, _ConfigurationService_) {
var html =
'<input type="text" ng-model="dateValue" transform-date-utc />';
ConfigurationService = _ConfigurationService_;
isTimezoneSetStub = sinon.stub(ConfigurationService, 'isTimezoneSet');
isTimezoneSetStub.returns(true);
timezoneStub = sinon.stub(ConfigurationService, 'timezone');
element = angular.element(html);
rootScope = $rootScope;
scope = $rootScope.$new();
scope.dateValue = null;
compile = function () {
$compile(element)(scope);
scope.$digest();
};
});
};
var shouldBehaveLikeAParser = function (value, expected) {
it('these should parse as expected', function () {
compile();
element.val(value);
element.trigger('input');
expect(scope.dateValue).to.eql(expected);
});
};
var shouldBehaveLikeAFormatter = function (value, expected) {
it('should format the value as expected', function () {
scope.dateValue = value;
compile();
expect(element.val()).to.eql(expected);
});
}
var shouldBehaveCorrectlyWhenGivenInvalidDateValues = function () {
describe('when given invalid date values', function () {
shouldBehaveLikeAParser(null, null);
shouldBehaveLikeAParser('invalid', '');
shouldBehaveLikeAParser('2016-12', '');
shouldBehaveLikeAFormatter('', '');
shouldBehaveLikeAFormatter('invalid', '');
shouldBehaveLikeAFormatter('2016-12', '');
});
};
context('when having UTC configured', function () {
beforeEach(function () {
prepare();
timezoneStub.returns('UTC');
});
describe('when given valid date values', function() {
var value = '2016-12-01';
var expectedValue = value + 'T00:00:00Z';
shouldBehaveLikeAParser(value, expectedValue);
shouldBehaveLikeAFormatter(expectedValue, value);
});
shouldBehaveCorrectlyWhenGivenInvalidDateValues();
});
context('when having GMT+1 zone configured', function () {
beforeEach(function () {
prepare();
// moment-timezone: GMT+1 is actually GMT-1
timezoneStub.returns('Etc/GMT+1');
});
describe('it has the time adjusted', function () {
var value = '2016-12-03T00:00:00Z';
var expectedValue = '2016-12-02';
shouldBehaveLikeAFormatter(value, expectedValue);
});
});
context('when having GMT-1 zone configured', function () {
beforeEach(function () {
prepare();
// moment-timezone: GMT-1 is actually GMT+1
timezoneStub.returns('Etc/GMT-1');
});
describe('it has the time adjusted', function () {
var value = '2016-12-01';
var expectedValue = '2016-11-30T23:00:00Z';
shouldBehaveLikeAParser(value, expectedValue);
});
});
});
Loading…
Cancel
Save