diff --git a/lib/content-services/src/lib/content-node-share/content-node-share.dialog.html b/lib/content-services/src/lib/content-node-share/content-node-share.dialog.html
index 034b6664d6..8422736942 100644
--- a/lib/content-services/src/lib/content-node-share/content-node-share.dialog.html
+++ b/lib/content-services/src/lib/content-node-share/content-node-share.dialog.html
@@ -51,7 +51,12 @@
[attr.aria-label]="'SHARE.EXPIRATION-LABEL' | translate"
[min]="minDate"
formControlName="time"
- [matDatepicker]="datePicker" />
+ (keydown)="preventIncorrectCharacters($event)"
+ (blur)="onTimeChanged()"
+ (keydown.enter)="datePickerInput.blur()"
+ [matDatepicker]="datePicker"/>
+ {{ 'SHARE.INVALID_DATE_ERROR' | translate }}
{{ 'SHARE.SHARE-LINK' | translate }}
diff --git a/lib/content-services/src/lib/content-node-share/content-node-share.dialog.spec.ts b/lib/content-services/src/lib/content-node-share/content-node-share.dialog.spec.ts
index 907d659765..a49f68c42e 100644
--- a/lib/content-services/src/lib/content-node-share/content-node-share.dialog.spec.ts
+++ b/lib/content-services/src/lib/content-node-share/content-node-share.dialog.spec.ts
@@ -21,12 +21,12 @@ import { of } from 'rxjs';
import { NotificationService, AppConfigService } from '@alfresco/adf-core';
import { NodesApiService } from '../common/services/nodes-api.service';
import { RenditionService } from '../common/services/rendition.service';
-
import { SharedLinksApiService } from './services/shared-links-api.service';
import { ShareDialogComponent } from './content-node-share.dialog';
import { ContentTestingModule } from '../testing/content.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { format, endOfDay } from 'date-fns';
+import { By } from '@angular/platform-browser';
describe('ShareDialogComponent', () => {
let node;
@@ -45,6 +45,17 @@ describe('ShareDialogComponent', () => {
const getShareToggleLinkedClasses = (): DOMTokenList => fixture.nativeElement.querySelector(shareToggleId).classList;
+ const fillInDatepickerInput = (value: string) => {
+ const input = fixture.debugElement.query(By.css('.adf-share-link__input')).nativeElement;
+ input.value = value;
+ input.dispatchEvent(new Event('input'));
+ input.dispatchEvent(new Event('blur'));
+ fixture.detectChanges();
+ };
+
+ const clickExpireToggleButton = () => fixture.nativeElement.querySelector('mat-slide-toggle[data-automation-id="adf-expire-toggle"] label')
+ .dispatchEvent(new MouseEvent('click'));
+
const clickShareToggleButton = () => fixture.nativeElement.querySelector(`${shareToggleId} label`)
.dispatchEvent(new MouseEvent('click'));
@@ -250,17 +261,10 @@ describe('ShareDialogComponent', () => {
};
fixture.detectChanges();
-
component.form.controls['time'].setValue(new Date());
-
fixture.detectChanges();
-
- fixture.nativeElement
- .querySelector('.mat-slide-toggle[data-automation-id="adf-expire-toggle"] label')
- .dispatchEvent(new MouseEvent('click'));
-
+ clickExpireToggleButton();
fixture.detectChanges();
-
await fixture.whenStable();
expect(sharedLinksApiService.deleteSharedLink).toHaveBeenCalled();
@@ -304,10 +308,10 @@ describe('ShareDialogComponent', () => {
spyOn(appConfigService, 'get').and.callFake(() => dateTimePickerType as any);
fixture.detectChanges();
- fixture.nativeElement.querySelector('mat-slide-toggle[data-automation-id="adf-expire-toggle"] label')
- .dispatchEvent(new MouseEvent('click'));
+ clickExpireToggleButton();
fixture.componentInstance.time.setValue(date);
+ component.onTimeChanged();
fixture.detectChanges();
tick(500);
@@ -326,11 +330,11 @@ describe('ShareDialogComponent', () => {
spyOn(appConfigService, 'get').and.returnValue(dateTimePickerType);
fixture.detectChanges();
- fixture.nativeElement.querySelector('mat-slide-toggle[data-automation-id="adf-expire-toggle"] label')
- .dispatchEvent(new MouseEvent('click'));
+ clickExpireToggleButton();
fixture.componentInstance.type = 'datetime';
fixture.componentInstance.time.setValue(date);
+ component.onTimeChanged();
fixture.detectChanges();
tick(100);
@@ -342,5 +346,44 @@ describe('ShareDialogComponent', () => {
expiresAt: expiryDate
});
}));
+
+ it('should not update node when provided date is less than minDate', () => {
+ fixture.detectChanges();
+ clickExpireToggleButton();
+ fillInDatepickerInput('01.01.2010');
+
+ expect(component.form.invalid).toBeTrue();
+ expect(sharedLinksApiService.deleteSharedLink).not.toHaveBeenCalled();
+ expect(sharedLinksApiService.createSharedLinks).not.toHaveBeenCalled();
+ });
+
+ it('should not accept alphabets in the datepicker input', () => {
+ fixture.detectChanges();
+ clickExpireToggleButton();
+ fillInDatepickerInput('test');
+
+ expect(component.form.invalid).toBeTrue();
+ expect(component.form.controls['time'].value).toBeNull();
+ });
+
+ it('should show an error if provided date is invalid', () => {
+ fixture.detectChanges();
+ clickExpireToggleButton();
+ fillInDatepickerInput('32');
+ const error = fixture.debugElement.query(By.css('[data-automation-id="adf-share-link-input-warning"]'));
+
+ expect(error).toBeTruthy();
+ expect(component.time.hasError('invalidDate')).toBeTrue();
+ });
+
+ it('should not show an error when provided date is valid', () => {
+ fixture.detectChanges();
+ clickExpireToggleButton();
+ fillInDatepickerInput('12.12.2525');
+ const error = fixture.debugElement.query(By.css('[data-automation-id="adf-share-link-input-warning"]'));
+
+ expect(error).toBeNull();
+ expect(component.time.hasError('invalidDate')).toBeFalse();
+ });
});
});
diff --git a/lib/content-services/src/lib/content-node-share/content-node-share.dialog.ts b/lib/content-services/src/lib/content-node-share/content-node-share.dialog.ts
index 5b48aaeb45..6beaaf69d4 100644
--- a/lib/content-services/src/lib/content-node-share/content-node-share.dialog.ts
+++ b/lib/content-services/src/lib/content-node-share/content-node-share.dialog.ts
@@ -18,17 +18,21 @@
import { Component, Inject, OnInit, ViewEncapsulation, ViewChild, OnDestroy } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
-import { UntypedFormGroup, UntypedFormControl, AbstractControl } from '@angular/forms';
+import {
+ UntypedFormGroup,
+ UntypedFormControl,
+ AbstractControl,
+ Validators,
+ ValidationErrors
+} from '@angular/forms';
import { Subject } from 'rxjs';
import { ContentService } from '../common/services/content.service';
-
import { SharedLinksApiService } from './services/shared-links-api.service';
import { SharedLinkBodyCreate, SharedLinkEntry } from '@alfresco/js-api';
import { ConfirmDialogComponent } from '../dialogs/confirm.dialog';
import { ContentNodeShareSettings } from './content-node-share.settings';
-import { takeUntil, debounceTime } from 'rxjs/operators';
import { RenditionService } from '../common/services/rendition.service';
-import { format, add, endOfDay } from 'date-fns';
+import { format, add, endOfDay, isBefore } from 'date-fns';
type DatePickerType = 'date' | 'time' | 'month' | 'datetime';
@@ -40,6 +44,10 @@ type DatePickerType = 'date' | 'time' | 'month' | 'datetime';
encapsulation: ViewEncapsulation.None
})
export class ShareDialogComponent implements OnInit, OnDestroy {
+ private minDateValidator = (control: AbstractControl): ValidationErrors | null => {
+ return isBefore(endOfDay(new Date(control.value)), this.minDate) ? {invalidDate: true} : null;
+ };
+
minDate = add(new Date(), { days: 1 });
sharedId: string;
fileName: string;
@@ -49,7 +57,7 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
isLinkWithExpiryDate = false;
form: UntypedFormGroup = new UntypedFormGroup({
sharedUrl: new UntypedFormControl(''),
- time: new UntypedFormControl({ value: '', disabled: true })
+ time: new UntypedFormControl({value: '', disabled: true}, [Validators.required, this.minDateValidator])
});
type: DatePickerType = 'date';
maxDebounceTime = 500;
@@ -90,12 +98,12 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
this.isLinkWithExpiryDate ? this.time.enable() : this.time.disable();
}
}
-
- this.time.valueChanges.pipe(debounceTime(this.maxDebounceTime), takeUntil(this.onDestroy$)).subscribe((value) => this.onTimeChanged(value));
}
- onTimeChanged(date: Date) {
- this.updateNode(date);
+ onTimeChanged() {
+ if (this.time.valid) {
+ this.updateNode(this.time.value);
+ }
}
get time(): AbstractControl {
@@ -137,12 +145,17 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
}
onDatePickerClosed() {
- this.datePickerInput.nativeElement.blur();
+ this.onTimeChanged();
if (!this.time.value) {
this.slideToggleExpirationDate.checked = false;
}
}
+ preventIncorrectCharacters(e: KeyboardEvent): boolean {
+ const regex = /[^\d/.-]/;
+ return e.key.length === 1 ? !regex.test(e.key) : true;
+ }
+
private openConfirmationDialog() {
this.isFileShared = false;
diff --git a/lib/content-services/src/lib/i18n/en.json b/lib/content-services/src/lib/i18n/en.json
index 022006237f..7b10cb8cf7 100644
--- a/lib/content-services/src/lib/i18n/en.json
+++ b/lib/content-services/src/lib/i18n/en.json
@@ -533,7 +533,8 @@
"REMOVE": "Remove"
},
"UNSHARE_ERROR": "Error unsharing this file",
- "UNSHARE_PERMISSION_ERROR": "You don't have permission to unshare this file"
+ "UNSHARE_PERMISSION_ERROR": "You don't have permission to unshare this file",
+ "INVALID_DATE_ERROR": "Invalid date"
},
"PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": {