[APPS-2108] break direct dependency on moment.js (#9032)

* break direct dependency on moment.js

* [ci:force] preserve moment for cli tools

* remove MatMomentDatetimeModule from content

* share dialog fixes

* revert testing module changes

* remove incorrect date modules

* fix html
This commit is contained in:
Denys Vuika
2023-10-26 14:33:26 +01:00
committed by GitHub
parent abf369bc37
commit 7ebdce7875
25 changed files with 82 additions and 160 deletions

View File

@@ -16,7 +16,6 @@ module.exports = function (config) {
included: true,
watched: false
},
{pattern: 'node_modules/moment/min/moment.min.js', included: true, watched: false},
{pattern: 'lib/content-services/src/lib/i18n/**/en.json', included: false, served: true, watched: false},
{
pattern: 'lib/content-services/src/lib/assets/images/**/*.svg',

View File

@@ -23,7 +23,6 @@
"@angular/router": ">=14.1.3",
"@alfresco/js-api": ">=7.1.0-1437",
"@ngx-translate/core": ">=14.0.0",
"moment": ">=2.22.2",
"@alfresco/adf-core": ">=6.3.0"
},
"keywords": [

View File

@@ -23,7 +23,7 @@
color="primary"
data-automation-id="adf-expire-toggle"
aria-label="{{ 'SHARE.EXPIRES' | translate }}"
[checked]="time.value"
[checked]="!!time.value"
(change)="onToggleExpirationDate($event)">
</mat-slide-toggle>
</div>

View File

@@ -27,9 +27,10 @@ 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';
import { NodeEntry } from '@alfresco/js-api';
describe('ShareDialogComponent', () => {
let node;
let node: NodeEntry;
let matDialog: MatDialog;
const notificationServiceMock = {
openSnackMessage: jasmine.createSpy('openSnackMessage')
@@ -42,6 +43,7 @@ describe('ShareDialogComponent', () => {
let appConfigService: AppConfigService;
const shareToggleId = '[data-automation-id="adf-share-toggle"]';
const expireToggle = '[data-automation-id="adf-expire-toggle"]';
const getShareToggleLinkedClasses = (): DOMTokenList => fixture.nativeElement.querySelector(shareToggleId).classList;
@@ -53,32 +55,26 @@ describe('ShareDialogComponent', () => {
fixture.detectChanges();
};
const clickExpireToggleButton = () => fixture.nativeElement.querySelector('mat-slide-toggle[data-automation-id="adf-expire-toggle"] label')
.dispatchEvent(new MouseEvent('click'));
const clickExpireToggleButton = () => fixture.nativeElement.querySelector(`${expireToggle} label`).dispatchEvent(new MouseEvent('click'));
const clickShareToggleButton = () => fixture.nativeElement.querySelector(`${shareToggleId} label`)
.dispatchEvent(new MouseEvent('click'));
const clickShareToggleButton = () => fixture.nativeElement.querySelector(`${shareToggleId} label`).dispatchEvent(new MouseEvent('click'));
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot(),
ContentTestingModule
],
imports: [TranslateModule.forRoot(), ContentTestingModule],
providers: [
{provide: NotificationService, useValue: notificationServiceMock},
{ provide: NotificationService, useValue: notificationServiceMock },
{
provide: MatDialogRef, useValue: {
close: () => {
}
provide: MatDialogRef,
useValue: {
close: () => {}
}
},
{provide: MAT_DIALOG_DATA, useValue: {}}
{ provide: MAT_DIALOG_DATA, useValue: {} }
]
});
fixture = TestBed.createComponent(ShareDialogComponent);
component = fixture.componentInstance;
component.maxDebounceTime = 0;
matDialog = TestBed.inject(MatDialog);
sharedLinksApiService = TestBed.inject(SharedLinksApiService);
@@ -89,8 +85,15 @@ describe('ShareDialogComponent', () => {
node = {
entry: {
id: 'nodeId',
name: 'node1',
nodeType: 'cm:content',
allowableOperations: ['update'],
isFile: true,
isFolder: false,
modifiedAt: null,
modifiedByUser: null,
createdAt: null,
createdByUser: null,
properties: {}
}
};
@@ -104,9 +107,7 @@ describe('ShareDialogComponent', () => {
describe('Error Handling', () => {
it('should emit a generic error when unshare fails', (done) => {
spyOn(sharedLinksApiService, 'deleteSharedLink').and.returnValue(
of(new Error(`{ "error": { "statusCode": 999 } }`))
);
spyOn(sharedLinksApiService, 'deleteSharedLink').and.returnValue(of(new Error(`{ "error": { "statusCode": 999 } }`)));
const sub = sharedLinksApiService.error.subscribe((err) => {
expect(err.statusCode).toBe(999);
@@ -119,9 +120,7 @@ describe('ShareDialogComponent', () => {
});
it('should emit permission error when unshare fails', (done) => {
spyOn(sharedLinksApiService, 'deleteSharedLink').and.returnValue(
of(new Error(`{ "error": { "statusCode": 403 } }`))
);
spyOn(sharedLinksApiService, 'deleteSharedLink').and.returnValue(of(new Error(`{ "error": { "statusCode": 403 } }`)));
const sub = sharedLinksApiService.error.subscribe((err) => {
expect(err.statusCode).toBe(403);
@@ -135,10 +134,12 @@ describe('ShareDialogComponent', () => {
});
it(`should toggle share action when property 'sharedId' does not exists`, () => {
spyOn(sharedLinksApiService, 'createSharedLinks').and.returnValue(of({
entry: {id: 'sharedId', sharedId: 'sharedId'}
}));
spyOn(renditionService, 'getNodeRendition').and.returnValue(Promise.resolve({url: '', mimeType: ''}));
spyOn(sharedLinksApiService, 'createSharedLinks').and.returnValue(
of({
entry: { id: 'sharedId', sharedId: 'sharedId' }
})
);
spyOn(renditionService, 'getNodeRendition').and.returnValue(Promise.resolve({ url: '', mimeType: '' }));
component.data = {
node,
@@ -154,10 +155,12 @@ describe('ShareDialogComponent', () => {
});
it(`should not toggle share action when file has 'sharedId' property`, async () => {
spyOn(sharedLinksApiService, 'createSharedLinks').and.returnValue(of({
entry: {id: 'sharedId', sharedId: 'sharedId'}
}));
spyOn(renditionService, 'getNodeRendition').and.returnValue(Promise.resolve({url: '', mimeType: ''}));
spyOn(sharedLinksApiService, 'createSharedLinks').and.returnValue(
of({
entry: { id: 'sharedId', sharedId: 'sharedId' }
})
);
spyOn(renditionService, 'getNodeRendition').and.returnValue(Promise.resolve({ url: '', mimeType: '' }));
node.entry.properties['qshare:sharedId'] = 'sharedId';
@@ -177,7 +180,7 @@ describe('ShareDialogComponent', () => {
});
it('should open a confirmation dialog when unshare button is triggered', () => {
spyOn(matDialog, 'open').and.returnValue({beforeClosed: () => of(false)} as any);
spyOn(matDialog, 'open').and.returnValue({ beforeClosed: () => of(false) } as any);
spyOn(sharedLinksApiService, 'deleteSharedLink').and.callThrough();
node.entry.properties['qshare:sharedId'] = 'sharedId';
@@ -197,7 +200,7 @@ describe('ShareDialogComponent', () => {
});
it('should unshare file when confirmation dialog returns true', fakeAsync(() => {
spyOn(matDialog, 'open').and.returnValue({beforeClosed: () => of(true)} as any);
spyOn(matDialog, 'open').and.returnValue({ beforeClosed: () => of(true) } as any);
spyOn(sharedLinksApiService, 'deleteSharedLink').and.returnValue(of({}));
node.entry.properties['qshare:sharedId'] = 'sharedId';
@@ -216,7 +219,7 @@ describe('ShareDialogComponent', () => {
}));
it('should not unshare file when confirmation dialog returns false', fakeAsync(() => {
spyOn(matDialog, 'open').and.returnValue({beforeClosed: () => of(false)} as any);
spyOn(matDialog, 'open').and.returnValue({ beforeClosed: () => of(false) } as any);
spyOn(sharedLinksApiService, 'deleteSharedLink').and.callThrough();
node.entry.properties['qshare:sharedId'] = 'sharedId';
@@ -283,12 +286,10 @@ describe('ShareDialogComponent', () => {
fixture.detectChanges();
await fixture.whenStable();
expect(fixture.nativeElement.querySelector('.mat-slide-toggle[data-automation-id="adf-expire-toggle"]')
.classList).toContain('mat-disabled');
expect(fixture.nativeElement.querySelector('.mat-slide-toggle[data-automation-id="adf-expire-toggle"]').classList).toContain('mat-disabled');
expect(fixture.nativeElement.querySelector('[data-automation-id="adf-slide-toggle-checked"]').style.display).toEqual('none');
});
describe('datetimepicker type', () => {
beforeEach(() => {
spyOn(sharedLinksApiService, 'createSharedLinks').and.returnValue(of());
@@ -324,29 +325,6 @@ describe('ShareDialogComponent', () => {
});
}));
it('should update node with input date and time when type is `datetime`', fakeAsync(() => {
const dateTimePickerType = 'datetime';
const date = new Date('2525-01-01 13:00:00');
spyOn(appConfigService, 'get').and.returnValue(dateTimePickerType);
fixture.detectChanges();
clickExpireToggleButton();
fixture.componentInstance.type = 'datetime';
fixture.componentInstance.time.setValue(date);
component.onTimeChanged();
fixture.detectChanges();
tick(100);
const expiryDate = format((new Date(date)), `yyyy-MM-dd'T'HH:mm:ss.SSSxx`);
expect(sharedLinksApiService.deleteSharedLink).toHaveBeenCalled();
expect(sharedLinksApiService.createSharedLinks).toHaveBeenCalledWith('nodeId', {
nodeId: 'nodeId',
expiresAt: expiryDate
});
}));
it('should not update node when provided date is less than minDate', () => {
fixture.detectChanges();
clickExpireToggleButton();
@@ -366,10 +344,14 @@ describe('ShareDialogComponent', () => {
expect(component.form.controls['time'].value).toBeNull();
});
it('should show an error if provided date is invalid', () => {
it('should show an error if provided date is invalid', async () => {
fixture.detectChanges();
clickExpireToggleButton();
fillInDatepickerInput('32');
fillInDatepickerInput('incorrect');
fixture.detectChanges();
await fixture.whenStable();
const error = fixture.debugElement.query(By.css('[data-automation-id="adf-share-link-input-warning"]'));
expect(error).toBeTruthy();

View File

@@ -18,17 +18,20 @@
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 { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms';
import { FormControl, FormGroup, Validators } 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 { SharedLinkBodyCreate } from '@alfresco/js-api';
import { ConfirmDialogComponent } from '../dialogs/confirm.dialog';
import { ContentNodeShareSettings } from './content-node-share.settings';
import { RenditionService } from '../common/services/rendition.service';
import { format, add, endOfDay, isBefore } from 'date-fns';
type DatePickerType = 'date' | 'time' | 'month' | 'datetime';
interface SharedDialogFormProps {
sharedUrl: FormControl<string>;
time: FormControl<Date>;
}
@Component({
selector: 'adf-share-dialog',
@@ -38,7 +41,7 @@ type DatePickerType = 'date' | 'time' | 'month' | 'datetime';
encapsulation: ViewEncapsulation.None
})
export class ShareDialogComponent implements OnInit, OnDestroy {
private minDateValidator = (control: AbstractControl): any =>
private minDateValidator = (control: FormControl<Date>): any =>
isBefore(endOfDay(new Date(control.value)), this.minDate) ? { invalidDate: true } : null;
minDate = add(new Date(), { days: 1 });
@@ -48,20 +51,15 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
isFileShared = false;
isDisabled = false;
isLinkWithExpiryDate = false;
form: FormGroup = new FormGroup({
form = new FormGroup<SharedDialogFormProps>({
sharedUrl: new FormControl(''),
time: new FormControl({ value: '', disabled: true }, [Validators.required, this.minDateValidator])
time: new FormControl({ value: null, disabled: true }, [Validators.required, this.minDateValidator])
});
type: DatePickerType = 'date';
maxDebounceTime = 500;
isExpiryDateToggleChecked: boolean;
@ViewChild('slideToggleExpirationDate', { static: true })
slideToggleExpirationDate;
@ViewChild('datePickerInput', { static: true })
datePickerInput;
private onDestroy$ = new Subject<boolean>();
constructor(
@@ -99,7 +97,7 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
}
}
get time(): AbstractControl {
get time(): FormControl<Date> {
return this.form.controls['time'];
}
@@ -177,7 +175,7 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
this.isDisabled = true;
this.sharedLinksApiService.createSharedLinks(nodeId, sharedLinkWithExpirySettings).subscribe(
(sharedLink: SharedLinkEntry) => {
(sharedLink) => {
if (sharedLink.entry) {
this.sharedId = sharedLink.entry.id;
if (this.data.node.entry.properties) {
@@ -267,11 +265,7 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
private updateNode(date: Date) {
let expiryDate: Date | string;
if (date) {
if (this.type === 'date') {
expiryDate = format(endOfDay(new Date(date)), `yyyy-MM-dd'T'HH:mm:ss.SSSxx`);
} else {
expiryDate = format(new Date(date), `yyyy-MM-dd'T'HH:mm:ss.SSSxx`);
}
expiryDate = format(endOfDay(new Date(date)), `yyyy-MM-dd'T'HH:mm:ss.SSSxx`);
} else {
expiryDate = null;
}

View File

@@ -25,7 +25,6 @@ import { FolderDialogComponent } from './folder.dialog';
import { NodeLockDialogComponent } from './node-lock.dialog';
import { ConfirmDialogComponent } from './confirm.dialog';
import { MatDatetimepickerModule } from '@mat-datetimepicker/core';
import { MatMomentDatetimeModule } from '@mat-datetimepicker/moment';
import { LibraryDialogComponent } from './library/library.dialog';
import { ContentDirectiveModule } from '../directives';
import { DownloadZipDialogModule } from './download-zip/download-zip.dialog.module';
@@ -37,7 +36,6 @@ import { DownloadZipDialogModule } from './download-zip/download-zip.dialog.modu
CoreModule,
FormsModule,
ReactiveFormsModule,
MatMomentDatetimeModule,
MatDatetimepickerModule,
ContentDirectiveModule,
DownloadZipDialogModule