[ACS-9758][ACS-9719] Refactor Folder Rules unit tests (#4709)

* [ACS-9758][ACS-9719] Refactor Folder Rules unit tests

* [ACS-9758] sonar issues

* [ACS-9758] fix test naming

* [ACS-9758] fix test naming

* [ACS-9758] cr fixes
This commit is contained in:
Mykyta Maliarchuk
2025-08-01 09:02:14 +02:00
committed by GitHub
parent 1b84ed7a17
commit b7dcb3b333
21 changed files with 1730 additions and 452 deletions

View File

@@ -23,29 +23,33 @@
*/ */
import { isFolderRulesEnabled } from './folder-rules.rules'; import { isFolderRulesEnabled } from './folder-rules.rules';
import { AcaRuleContext } from '@alfresco/aca-shared/rules';
import { AppConfigService } from '@alfresco/adf-core';
describe('Folder Rules Visibility Rules', () => { describe('Folder Rules Visibility Rules', () => {
describe('isFolderRulesEnabled', () => { let mockAppConfig: jasmine.SpyObj<AppConfigService>;
it('should have the feature enabled', () => { let context: Partial<AcaRuleContext>;
const context: any = {
appConfig: {
get: () => true
}
};
const result = isFolderRulesEnabled(context); beforeEach(() => {
expect(result).toEqual(true); mockAppConfig = jasmine.createSpyObj<AppConfigService>('AppConfigService', ['get']);
}); context = {
appConfig: mockAppConfig
};
});
it('should have the feature disabled', () => { it('should return true when plugin is enabled in app config', () => {
const context: any = { mockAppConfig.get.and.returnValue(true);
appConfig: { const result = isFolderRulesEnabled(context as AcaRuleContext);
get: () => false
}
};
const result = isFolderRulesEnabled(context); expect(context.appConfig.get).toHaveBeenCalledWith('plugins.folderRules', false);
expect(result).toEqual(false); expect(result).toBe(true);
}); });
it('should return false when plugin is disabled in app config', () => {
mockAppConfig.get.and.returnValue(false);
const result = isFolderRulesEnabled(context as AcaRuleContext);
expect(context.appConfig.get).toHaveBeenCalledWith('plugins.folderRules', false);
expect(result).toBe(false);
}); });
}); });

View File

@@ -22,9 +22,8 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>. * from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { ManageRulesSmartComponent } from './manage-rules.smart-component'; import { ManageRulesSmartComponent } from './manage-rules.smart-component';
import { DebugElement, Predicate } from '@angular/core';
import { FolderRulesService } from '../services/folder-rules.service'; import { FolderRulesService } from '../services/folder-rules.service';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { BehaviorSubject, of, Subject } from 'rxjs'; import { BehaviorSubject, of, Subject } from 'rxjs';
@@ -35,32 +34,69 @@ import {
ownedRuleSetMock, ownedRuleSetMock,
ruleSetWithLinkMock ruleSetWithLinkMock
} from '../mock/rule-sets.mock'; } from '../mock/rule-sets.mock';
import { By } from '@angular/platform-browser'; import { owningFolderIdMock, owningFolderMock } from '../mock/node.mock';
import { getOwningFolderEntryMock, owningFolderIdMock, owningFolderMock } from '../mock/node.mock'; import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { MatDialog } from '@angular/material/dialog';
import { ActionsService } from '../services/actions.service'; import { ActionsService } from '../services/actions.service';
import { FolderRuleSetsService } from '../services/folder-rule-sets.service'; import { FolderRuleSetsService } from '../services/folder-rule-sets.service';
import { ruleMock, ruleSettingsMock } from '../mock/rules.mock'; import { ruleMock, ruleSettingsMock } from '../mock/rules.mock';
import { AppService } from '@alfresco/aca-shared'; import { AppService, GenericErrorComponent } from '@alfresco/aca-shared';
import { HarnessLoader } from '@angular/cdk/testing'; import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatProgressBarHarness } from '@angular/material/progress-bar/testing'; import { MatProgressBarHarness } from '@angular/material/progress-bar/testing';
import { MatSlideToggleHarness } from '@angular/material/slide-toggle/testing'; import { MatSlideToggleHarness } from '@angular/material/slide-toggle/testing';
import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-content-services'; import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-content-services';
import { provideHttpClient } from '@angular/common/http'; import { provideHttpClient } from '@angular/common/http';
import { NoopTranslateModule } from '@alfresco/adf-core'; import { EmptyContentComponent, NoopTranslateModule, NotificationService, UnitTestingUtils } from '@alfresco/adf-core';
import { provideMockStore } from '@ngrx/store/testing'; import { provideMockStore } from '@ngrx/store/testing';
import { Location } from '@angular/common';
import { DebugElement } from '@angular/core';
import { EditRuleDialogUiComponent } from '../rule-details/edit-rule-dialog.ui-component';
import { Rule } from '../model/rule.model';
import { RuleDetailsUiComponent } from '../rule-details/rule-details.ui-component';
describe('ManageRulesSmartComponent', () => { describe('ManageRulesSmartComponent', () => {
let fixture: ComponentFixture<ManageRulesSmartComponent>; let fixture: ComponentFixture<ManageRulesSmartComponent>;
let component: ManageRulesSmartComponent; let component: ManageRulesSmartComponent;
let debugElement: DebugElement;
let loader: HarnessLoader; let loader: HarnessLoader;
let dialog: MatDialog;
let unitTestingUtils: UnitTestingUtils;
let folderRuleSetsService: FolderRuleSetsService; let folderRuleSetsService: FolderRuleSetsService;
let folderRulesService: FolderRulesService; let folderRulesService: FolderRulesService;
let actionsService: ActionsService; let actionsService: ActionsService;
let callApiSpy: jasmine.Spy;
const setupBasicObservables = () => {
folderRuleSetsService.folderInfo$ = of(owningFolderMock);
folderRuleSetsService.isLoading$ = of(false);
actionsService.loading$ = of(false);
folderRulesService.deletedRuleId$ = of(null);
};
const setupWithMainRuleSet = (ruleSet = ownedRuleSetMock) => {
setupBasicObservables();
folderRuleSetsService.mainRuleSet$ = of(ruleSet);
folderRuleSetsService.inheritedRuleSets$ = of([inheritedRuleSetMock]);
folderRulesService.selectedRule$ = of(ruleMock('owned-rule-1'));
};
const setupWithoutMainRuleSet = (inheritedRuleSets = [inheritedRuleSetWithEmptyRulesMock]) => {
setupBasicObservables();
folderRuleSetsService.mainRuleSet$ = of(null);
folderRuleSetsService.inheritedRuleSets$ = of(inheritedRuleSets);
folderRulesService.selectedRule$ = of(ruleMock('owned-rule-1'));
};
const setupLoadingState = () => {
folderRuleSetsService.folderInfo$ = of(null);
folderRuleSetsService.mainRuleSet$ = of(null);
folderRuleSetsService.inheritedRuleSets$ = of([]);
folderRuleSetsService.isLoading$ = of(true);
actionsService.loading$ = of(true);
};
const getRules = (): DebugElement[] => unitTestingUtils.getAllByCSS('.aca-rule-list-item');
const getRuleSets = (): DebugElement[] => unitTestingUtils.getAllByCSS(`[data-automation-id="rule-set-list-item"]`);
const getRuleDetails = (): DebugElement => unitTestingUtils.getByDirective(RuleDetailsUiComponent);
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -81,9 +117,10 @@ describe('ManageRulesSmartComponent', () => {
}); });
fixture = TestBed.createComponent(ManageRulesSmartComponent); fixture = TestBed.createComponent(ManageRulesSmartComponent);
dialog = TestBed.inject(MatDialog);
component = fixture.componentInstance; component = fixture.componentInstance;
debugElement = fixture.debugElement;
loader = TestbedHarnessEnvironment.loader(fixture); loader = TestbedHarnessEnvironment.loader(fixture);
unitTestingUtils = new UnitTestingUtils(fixture.debugElement, loader);
folderRuleSetsService = TestBed.inject(FolderRuleSetsService); folderRuleSetsService = TestBed.inject(FolderRuleSetsService);
folderRulesService = TestBed.inject(FolderRulesService); folderRulesService = TestBed.inject(FolderRulesService);
@@ -91,85 +128,84 @@ describe('ManageRulesSmartComponent', () => {
spyOn(actionsService, 'loadActionDefinitions').and.stub(); spyOn(actionsService, 'loadActionDefinitions').and.stub();
spyOn(folderRulesService, 'getRuleSettings').and.returnValue(Promise.resolve(ruleSettingsMock)); spyOn(folderRulesService, 'getRuleSettings').and.returnValue(Promise.resolve(ruleSettingsMock));
callApiSpy = spyOn<any>(folderRuleSetsService, 'callApi'); spyOn(folderRuleSetsService, 'loadRuleSets');
callApiSpy
.withArgs(`/nodes/${owningFolderIdMock}/rule-sets?include=isLinkedTo,owningFolder,linkedToBy&skipCount=0&maxItems=100`, 'GET')
.and.returnValue(Promise.resolve(ownedRuleSetMock))
.withArgs(`/nodes/${owningFolderIdMock}/rule-sets/-default-?include=isLinkedTo,owningFolder,linkedToBy`, 'GET')
.and.returnValue(Promise.resolve(ownedRuleSetMock))
.withArgs(`/nodes/${owningFolderIdMock}?include=path%2Cproperties%2CallowableOperations%2Cpermissions`, 'GET')
.and.returnValue(Promise.resolve(getOwningFolderEntryMock));
}); });
afterEach(() => { afterEach(() => {
fixture.destroy(); fixture.destroy();
}); });
it('should call location.back() when goBack is called', () => {
const locationService = TestBed.inject(Location);
spyOn(locationService, 'back');
component.goBack();
expect(locationService.back).toHaveBeenCalled();
});
it('should call folderRulesService.selectRule with provided rule on rule select', () => {
const testRule = ruleMock('test-rule-1');
spyOn(folderRulesService, 'selectRule');
component.onSelectRule(testRule);
expect(folderRulesService.selectRule).toHaveBeenCalledWith(testRule);
});
it('should call loadMoreInheritedRuleSets on load more rule sets', () => {
spyOn(folderRuleSetsService, 'loadMoreInheritedRuleSets');
component.onLoadMoreRuleSets();
expect(folderRuleSetsService.loadMoreInheritedRuleSets).toHaveBeenCalled();
});
it('should load rules for the given rule set', () => {
spyOn(folderRulesService, 'loadRules');
const ruleSet = inheritedRuleSetMock;
component.onLoadMoreRules(ruleSet);
expect(folderRulesService.loadRules).toHaveBeenCalledWith(ruleSet);
});
it('should show a list of rule sets and rules', () => { it('should show a list of rule sets and rules', () => {
const loadRuleSetsSpy = spyOn(folderRuleSetsService, 'loadRuleSets').and.stub(); setupWithMainRuleSet();
folderRuleSetsService.folderInfo$ = of(owningFolderMock);
folderRuleSetsService.mainRuleSet$ = of(ownedRuleSetMock);
folderRuleSetsService.inheritedRuleSets$ = of([inheritedRuleSetMock]);
folderRuleSetsService.isLoading$ = of(false);
folderRulesService.selectedRule$ = of(ruleMock('owned-rule-1'));
actionsService.loading$ = of(false);
fixture.detectChanges(); fixture.detectChanges();
expect(component).toBeTruthy(); expect(component).toBeTruthy();
expect(folderRuleSetsService.loadRuleSets).toHaveBeenCalledOnceWith(component.nodeId);
expect(loadRuleSetsSpy).toHaveBeenCalledOnceWith(component.nodeId); const ruleGroupingSections = unitTestingUtils.getAllByCSS(`[data-automation-id="rule-list-item"]`);
const ruleGroupingSections = debugElement.queryAll(By.css(`[data-automation-id="rule-list-item"]`)); const deleteRuleBtn = unitTestingUtils.getByCSS('#delete-rule-btn');
const rules = debugElement.queryAll(By.css('.aca-rule-list-item'));
const ruleDetails = debugElement.query(By.css('aca-rule-details'));
const deleteRuleBtn = debugElement.query(By.css('#delete-rule-btn'));
expect(ruleGroupingSections.length).toBe(2, 'unexpected number of rule sections'); expect(ruleGroupingSections.length).toBe(2, 'unexpected number of rule sections');
expect(rules.length).toBe(4, 'unexpected number of aca-rule-list-item'); expect(getRules().length).toBe(4, 'unexpected number of aca-rule-list-item');
expect(ruleDetails).toBeTruthy('aca-rule-details was not rendered'); expect(getRuleDetails()).toBeTruthy('aca-rule-details was not rendered');
expect(deleteRuleBtn).toBeTruthy('no delete rule button'); expect(deleteRuleBtn).toBeTruthy('no delete rule button');
}); });
it('should show adf-empty-content if node has no rules defined yet', () => { it('should show adf-empty-content if node has no rules defined yet', () => {
folderRuleSetsService.folderInfo$ = of(owningFolderMock); setupWithoutMainRuleSet();
folderRuleSetsService.mainRuleSet$ = of(null);
folderRuleSetsService.inheritedRuleSets$ = of([inheritedRuleSetWithEmptyRulesMock]);
folderRuleSetsService.isLoading$ = of(false);
actionsService.loading$ = of(false);
fixture.detectChanges(); fixture.detectChanges();
expect(component).toBeTruthy(); expect(component).toBeTruthy();
const adfEmptyContent = debugElement.query(By.css('adf-empty-content')); const adfEmptyContent = unitTestingUtils.getByDirective(EmptyContentComponent);
const ruleSets = debugElement.queryAll(By.css(`[data-automation-id="rule-set-list-item"]`));
const ruleDetails = debugElement.query(By.css('aca-rule-details'));
expect(adfEmptyContent).toBeTruthy(); expect(adfEmptyContent).toBeTruthy();
expect(ruleSets.length).toBe(0); expect(getRuleSets().length).toBe(0);
expect(ruleDetails).toBeFalsy(); expect(getRuleDetails()).toBeFalsy();
}); });
it('should show adf-empty-content if there are only inherited disabled rules', () => { it('should show adf-empty-content if there are only inherited disabled rules', () => {
folderRuleSetsService.folderInfo$ = of(owningFolderMock); setupWithoutMainRuleSet([inheritedRuleSetWithOnlyDisabledRulesMock]);
folderRuleSetsService.mainRuleSet$ = of(null);
folderRuleSetsService.inheritedRuleSets$ = of([inheritedRuleSetWithOnlyDisabledRulesMock]);
folderRuleSetsService.isLoading$ = of(false);
actionsService.loading$ = of(false);
fixture.detectChanges(); fixture.detectChanges();
expect(component).toBeTruthy(); expect(component).toBeTruthy();
const adfEmptyContent = debugElement.query(By.css('adf-empty-content')); const adfEmptyContent = unitTestingUtils.getByDirective(EmptyContentComponent);
const ruleSets = debugElement.queryAll(By.css(`[data-automation-id="rule-set-list-item"]`));
const ruleDetails = debugElement.query(By.css('aca-rule-details'));
expect(adfEmptyContent).toBeTruthy(); expect(adfEmptyContent).toBeTruthy();
expect(ruleSets.length).toBe(0); expect(getRuleSets().length).toBe(0);
expect(ruleDetails).toBeFalsy(); expect(getRuleDetails()).toBeFalsy();
}); });
it('should only show aca-generic-error if the non-existing node was provided', () => { it('should only show aca-generic-error if the non-existing node was provided', () => {
@@ -183,129 +219,203 @@ describe('ManageRulesSmartComponent', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
const acaGenericError = debugElement.query(By.css('aca-generic-error')); const acaGenericError = unitTestingUtils.getByDirective(GenericErrorComponent);
const rules = debugElement.query(By.css('.aca-rule-list-item'));
const ruleDetails = debugElement.query(By.css('aca-rule-details'));
expect(acaGenericError).toBeTruthy(); expect(acaGenericError).toBeTruthy();
expect(rules).toBeFalsy(); expect(getRules().length).toBeFalsy();
expect(ruleDetails).toBeFalsy(); expect(getRuleDetails()).toBeFalsy();
}); });
it('should only show progress bar while loading', async () => { it('should only show progress bar while loading', async () => {
folderRuleSetsService.folderInfo$ = of(null); setupLoadingState();
folderRuleSetsService.mainRuleSet$ = of(null);
folderRuleSetsService.inheritedRuleSets$ = of([]);
folderRuleSetsService.isLoading$ = of(true);
actionsService.loading$ = of(true);
fixture.detectChanges(); fixture.detectChanges();
expect(component).toBeTruthy(); expect(component).toBeTruthy();
const matProgressBar = loader.getHarness(MatProgressBarHarness); const matProgressBar = loader.getHarness(MatProgressBarHarness);
const rules = debugElement.query(By.css('.aca-rule-list-item'));
const ruleDetails = debugElement.query(By.css('aca-rule-details'));
expect(matProgressBar).toBeTruthy(); expect(matProgressBar).toBeTruthy();
expect(rules).toBeFalsy(); expect(getRules().length).toBeFalsy();
expect(ruleDetails).toBeFalsy(); expect(getRuleDetails()).toBeFalsy();
}); });
// TODO: [ACS-9719] flaky test that needs review it('should call deleteRule() if confirmation dialog returns true', () => {
// eslint-disable-next-line ban/ban setupWithMainRuleSet();
xit('should call deleteRule() if confirmation dialog returns true', () => { fixture.detectChanges();
const dialog = TestBed.inject(MatDialog);
folderRuleSetsService.folderInfo$ = of(owningFolderMock);
folderRuleSetsService.mainRuleSet$ = of(ownedRuleSetMock);
folderRuleSetsService.inheritedRuleSets$ = of([inheritedRuleSetMock]);
folderRuleSetsService.isLoading$ = of(false);
folderRulesService.selectedRule$ = of(ruleMock('owned-rule-1'));
folderRulesService.deletedRuleId$ = of(null);
actionsService.loading$ = of(false);
const onRuleDeleteButtonClickedSpy = spyOn(component, 'onRuleDeleteButtonClicked').and.callThrough(); spyOn(dialog, 'open').and.returnValue({
const dialogResult: any = {
afterClosed: () => of(true) afterClosed: () => of(true)
}; } as MatDialogRef<boolean>);
const dialogOpenSpy = spyOn(dialog, 'open').and.returnValue(dialogResult);
const deleteRuleSpy = spyOn(folderRulesService, 'deleteRule'); const deleteRuleSpy = spyOn(folderRulesService, 'deleteRule');
const onRuleDeleteSpy = spyOn(component, 'onRuleDelete').and.callThrough();
fixture.detectChanges(); fixture.detectChanges();
expect(component).toBeTruthy('expected component'); expect(component).toBeTruthy('expected component');
const rules = debugElement.queryAll(By.css('.aca-rule-list-item')); component.onRuleDeleteButtonClicked(ruleMock('owned-rule-1'));
const ruleDetails = debugElement.query(By.css('aca-rule-details'));
const deleteRuleBtn = fixture.debugElement.nativeElement.querySelector('#delete-rule-btn');
deleteRuleBtn.click();
fixture.detectChanges(); fixture.detectChanges();
folderRulesService.deletedRuleId$ = of('owned-rule-1-id'); folderRulesService.deletedRuleId$ = of('owned-rule-1-id');
expect(onRuleDeleteButtonClickedSpy).toHaveBeenCalled(); expect(dialog.open).toHaveBeenCalled();
expect(dialogOpenSpy).toHaveBeenCalled();
expect(deleteRuleSpy).toHaveBeenCalled(); expect(deleteRuleSpy).toHaveBeenCalled();
expect(onRuleDeleteSpy).toHaveBeenCalledTimes(1); expect(getRules()).toBeTruthy('expected rules');
expect(rules).toBeTruthy('expected rules'); expect(getRuleDetails()).toBeTruthy('expected ruleDetails');
expect(ruleDetails).toBeTruthy('expected ruleDetails'); });
expect(deleteRuleBtn).toBeTruthy();
it('should refresh main rule set when link rules dialog is closed', () => {
setupWithMainRuleSet();
fixture.detectChanges();
spyOn(dialog, 'open').and.returnValue({
afterClosed: () => of(true)
} as MatDialogRef<boolean>);
const refreshMainRuleSetSpy = spyOn(folderRuleSetsService, 'refreshMainRuleSet');
fixture.detectChanges();
expect(component).toBeTruthy('expected component');
component.openLinkRulesDialog();
fixture.detectChanges();
folderRulesService.deletedRuleId$ = of('owned-rule-1-id');
expect(dialog.open).toHaveBeenCalled();
expect(refreshMainRuleSetSpy).toHaveBeenCalled();
expect(getRules()).toBeTruthy('expected rules');
expect(getRuleDetails()).toBeTruthy('expected ruleDetails');
});
it('should call deleteRuleSetLink when onRuleSetUnlinkClicked is called', () => {
setupWithMainRuleSet();
fixture.detectChanges();
spyOn(dialog, 'open').and.returnValue({
afterClosed: () => of(true)
} as MatDialogRef<boolean>);
const deleteRuleSetLinkSpy = spyOn(folderRuleSetsService, 'deleteRuleSetLink');
fixture.detectChanges();
expect(component).toBeTruthy('expected component');
component.onRuleSetUnlinkClicked(ruleSetWithLinkMock);
fixture.detectChanges();
folderRulesService.deletedRuleId$ = of('owned-rule-1-id');
expect(dialog.open).toHaveBeenCalled();
expect(deleteRuleSetLinkSpy).toHaveBeenCalled();
expect(getRules()).toBeTruthy('expected rules');
expect(getRuleDetails()).toBeTruthy('expected ruleDetails');
});
describe('EditRuleDialog', () => {
let submit$: Subject<Partial<Rule>>;
let dialogRefMock: jasmine.SpyObj<MatDialogRef<EditRuleDialogUiComponent, boolean>>;
beforeEach(() => {
submit$ = new Subject<Partial<Rule>>();
dialogRefMock = jasmine.createSpyObj<MatDialogRef<EditRuleDialogUiComponent, boolean>>('MatDialogRef', ['close', 'afterClosed']);
dialogRefMock.afterClosed.and.returnValue(of(true));
dialogRefMock.componentInstance = { submitted: submit$ } as EditRuleDialogUiComponent;
spyOn(dialog, 'open').and.returnValue(dialogRefMock as MatDialogRef<EditRuleDialogUiComponent, boolean>);
});
it('should open EditRuleDialogUiComponent with correct config', () => {
const model = { name: 'bar' } as Rule;
component.openCreateUpdateRuleDialog(model);
expect(dialog.open).toHaveBeenCalled();
});
it('should create a new rule and close dialog when submitted without id', async () => {
const newRuleParams = { name: 'NewRule' } as Rule;
const createdRule = ruleMock('new-id');
spyOn(folderRulesService, 'createRule').and.returnValue(Promise.resolve(createdRule));
const addOrUpdateSpy = spyOn(folderRuleSetsService, 'addOrUpdateRuleInMainRuleSet');
component.openCreateUpdateRuleDialog();
submit$.next(newRuleParams);
await Promise.resolve();
expect(folderRulesService.createRule).toHaveBeenCalledWith(component.nodeId, newRuleParams);
expect(addOrUpdateSpy).toHaveBeenCalledWith(createdRule);
});
it('should update existing rule when submitted with id', async () => {
const updatedRuleParams = { id: '123', name: 'Test' } as Rule;
const updatedRule = ruleMock('123');
spyOn(folderRulesService, 'updateRule').and.returnValue(Promise.resolve(updatedRule));
const addOrUpdateSpy = spyOn(folderRuleSetsService, 'addOrUpdateRuleInMainRuleSet');
component.openCreateUpdateRuleDialog();
submit$.next(updatedRuleParams);
await Promise.resolve();
expect(folderRulesService.updateRule).toHaveBeenCalled();
expect(addOrUpdateSpy).toHaveBeenCalledWith(updatedRule);
});
it('should show error notification if submission fails', fakeAsync(() => {
const notificationService = TestBed.inject(NotificationService);
const params = { name: 'Bad' } as Rule;
const error = new Error('Test error');
(error as any).response = { body: { error: { errorKey: 'ERROR_KEY' } } };
spyOn(folderRulesService, 'createRule').and.returnValue(Promise.reject(error));
spyOn(notificationService, 'showError');
component.openCreateUpdateRuleDialog();
submit$.next(params);
tick();
expect(notificationService.showError).toHaveBeenCalledWith('ERROR_KEY');
expect(dialogRefMock.close).not.toHaveBeenCalled();
}));
}); });
describe('Create rule & link rules buttons visibility', () => { describe('Create rule & link rules buttons visibility', () => {
let createButtonPredicate: Predicate<DebugElement>; const getCreateButton = (): DebugElement => unitTestingUtils.getByDataAutomationId('manage-rules-create-button');
let linkButtonPredicate: Predicate<DebugElement>; const getLinkButton = (): DebugElement => unitTestingUtils.getByDataAutomationId('manage-rules-link-button');
beforeEach(() => { beforeEach(() => {
folderRuleSetsService.folderInfo$ = of(owningFolderMock); folderRuleSetsService.folderInfo$ = of(owningFolderMock);
folderRuleSetsService.inheritedRuleSets$ = of([]); folderRuleSetsService.inheritedRuleSets$ = of([]);
folderRuleSetsService.isLoading$ = of(false); folderRuleSetsService.isLoading$ = of(false);
actionsService.loading$ = of(false); actionsService.loading$ = of(false);
createButtonPredicate = By.css(`[data-automation-id="manage-rules-create-button"]`);
linkButtonPredicate = By.css(`[data-automation-id="manage-rules-link-button"]`);
}); });
it('should show the create rule button if there is no main rule set', () => { it('should show the create rule button if there is no main rule set', () => {
folderRuleSetsService.mainRuleSet$ = of(null); folderRuleSetsService.mainRuleSet$ = of(null);
fixture.detectChanges(); fixture.detectChanges();
const createButton = debugElement.query(createButtonPredicate); expect(getCreateButton()).toBeTruthy();
expect(createButton).toBeTruthy();
}); });
it('should show the link rules button if there is no main rule set', () => { it('should show the link rules button if there is no main rule set', () => {
folderRuleSetsService.mainRuleSet$ = of(null); folderRuleSetsService.mainRuleSet$ = of(null);
fixture.detectChanges(); fixture.detectChanges();
const linkButton = debugElement.query(linkButtonPredicate); expect(getLinkButton()).toBeTruthy();
expect(linkButton).toBeTruthy();
}); });
it('should show the create rule button if the main rule set is owned', () => { it('should show the create rule button if the main rule set is owned', () => {
folderRuleSetsService.mainRuleSet$ = of(ownedRuleSetMock); folderRuleSetsService.mainRuleSet$ = of(ownedRuleSetMock);
fixture.detectChanges(); fixture.detectChanges();
const createButton = debugElement.query(createButtonPredicate); expect(getCreateButton()).toBeTruthy();
expect(createButton).toBeTruthy();
}); });
it('should not show the create rule button if the main rule set is linked', () => { it('should not show the create rule button if the main rule set is linked', () => {
folderRuleSetsService.mainRuleSet$ = of(ruleSetWithLinkMock); folderRuleSetsService.mainRuleSet$ = of(ruleSetWithLinkMock);
fixture.detectChanges(); fixture.detectChanges();
const createButton = debugElement.query(createButtonPredicate); expect(getCreateButton()).toBeFalsy();
expect(createButton).toBeFalsy();
}); });
it('should not show the link rules button if the folder has a main rule set', () => { it('should not show the link rules button if the folder has a main rule set', () => {
folderRuleSetsService.mainRuleSet$ = of(ownedRuleSetMock); folderRuleSetsService.mainRuleSet$ = of(ownedRuleSetMock);
fixture.detectChanges(); fixture.detectChanges();
const linkButton = debugElement.query(linkButtonPredicate); expect(getLinkButton()).toBeFalsy();
expect(linkButton).toBeFalsy();
}); });
}); });
@@ -331,22 +441,36 @@ describe('ManageRulesSmartComponent', () => {
expect(await createButton.isDisabled()).toBeTrue(); expect(await createButton.isDisabled()).toBeTrue();
}); });
it('should call onInheritanceToggleChange() on change', () => { it('should call onInheritanceToggleChange() on change', fakeAsync(() => {
const onInheritanceToggleChangeSpy = spyOn(component, 'onInheritanceToggleChange').and.callThrough();
const updateRuleSettingsSpy = spyOn(folderRulesService, 'updateRuleSettings').and.returnValue(Promise.resolve(ruleSettingsMock)); const updateRuleSettingsSpy = spyOn(folderRulesService, 'updateRuleSettings').and.returnValue(Promise.resolve(ruleSettingsMock));
const loadRuleSetsSpy = spyOn(folderRuleSetsService, 'loadRuleSets').and.callThrough();
fixture.detectChanges(); fixture.detectChanges();
const inheritanceToggleBtn = fixture.debugElement.query(By.css(`[data-automation-id="manage-rules-inheritance-toggle-button"]`)); const inheritanceToggleBtn = unitTestingUtils.getByDataAutomationId('manage-rules-inheritance-toggle-button');
inheritanceToggleBtn.nativeElement.dispatchEvent(new Event('change')); inheritanceToggleBtn.nativeElement.dispatchEvent(new Event('change'));
tick();
fixture.detectChanges(); fixture.detectChanges();
expect(onInheritanceToggleChangeSpy).toHaveBeenCalled(); expect(updateRuleSettingsSpy).toHaveBeenCalled();
expect(updateRuleSettingsSpy).toHaveBeenCalledTimes(1); expect(folderRuleSetsService.loadRuleSets).toHaveBeenCalled();
expect(loadRuleSetsSpy).toHaveBeenCalledTimes(1); expect(folderRuleSetsService.loadRuleSets).toHaveBeenCalledWith(component.nodeId);
}); expect(component.isInheritanceToggleDisabled).toBeFalse();
}));
it('should update rule enabled state and add or update it in main rule set', fakeAsync(() => {
const original = ruleMock('test');
const toggled: Rule = { ...original, isEnabled: !original.isEnabled };
spyOn(folderRulesService, 'updateRule').and.returnValue(Promise.resolve(toggled));
const addOrUpdateSpy = spyOn(folderRuleSetsService, 'addOrUpdateRuleInMainRuleSet');
component.nodeId = 'node-123';
component.onRuleEnabledToggle(original, toggled.isEnabled);
tick();
expect(folderRulesService.updateRule).toHaveBeenCalledWith('node-123', original.id, { ...original, isEnabled: toggled.isEnabled });
expect(addOrUpdateSpy).toHaveBeenCalledWith(toggled);
}));
}); });
}); });

View File

@@ -31,7 +31,7 @@ import { ActivatedRoute, RouterModule } from '@angular/router';
import { NodeInfo } from '@alfresco/aca-shared/store'; import { NodeInfo } from '@alfresco/aca-shared/store';
import { delay } from 'rxjs/operators'; import { delay } from 'rxjs/operators';
import { EditRuleDialogUiComponent } from '../rule-details/edit-rule-dialog.ui-component'; import { EditRuleDialogUiComponent } from '../rule-details/edit-rule-dialog.ui-component';
import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { ConfirmDialogComponent, EmptyContentComponent, NotificationService, ToolbarComponent, ToolbarTitleComponent } from '@alfresco/adf-core'; import { ConfirmDialogComponent, EmptyContentComponent, NotificationService, ToolbarComponent, ToolbarTitleComponent } from '@alfresco/adf-core';
import { ActionDefinitionTransformed } from '../model/rule-action.model'; import { ActionDefinitionTransformed } from '../model/rule-action.model';
import { ActionsService } from '../services/actions.service'; import { ActionsService } from '../services/actions.service';
@@ -64,7 +64,6 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
RouterModule, RouterModule,
GenericErrorComponent, GenericErrorComponent,
RuleDetailsUiComponent, RuleDetailsUiComponent,
MatDialogModule,
EmptyContentComponent, EmptyContentComponent,
ToolbarTitleComponent, ToolbarTitleComponent,
ToolbarComponent ToolbarComponent

View File

@@ -52,6 +52,7 @@ export interface RuleForForm {
id: string; id: string;
name: string; name: string;
description: string; description: string;
isShared: boolean;
triggers: RuleTrigger[]; triggers: RuleTrigger[];
conditions: RuleCompositeCondition; conditions: RuleCompositeCondition;
actions: RuleAction[]; actions: RuleAction[];

View File

@@ -23,9 +23,8 @@
*/ */
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopTranslateModule } from '@alfresco/adf-core'; import { NoopTranslateModule, UnitTestingUtils } from '@alfresco/adf-core';
import { RuleActionListUiComponent } from './rule-action-list.ui-component'; import { RuleActionListUiComponent } from './rule-action-list.ui-component';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core'; import { DebugElement } from '@angular/core';
import { RuleActionUiComponent } from './rule-action.ui-component'; import { RuleActionUiComponent } from './rule-action.ui-component';
import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-content-services'; import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-content-services';
@@ -33,9 +32,10 @@ import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-conten
describe('RuleActionListUiComponent', () => { describe('RuleActionListUiComponent', () => {
let fixture: ComponentFixture<RuleActionListUiComponent>; let fixture: ComponentFixture<RuleActionListUiComponent>;
let component: RuleActionListUiComponent; let component: RuleActionListUiComponent;
let unitTestingUtils: UnitTestingUtils;
const getByDataAutomationId = (dataAutomationId: string, index = 0): DebugElement => const getByDataAutomationId = (dataAutomationId: string, index = 0): DebugElement =>
fixture.debugElement.queryAll(By.css(`[data-automation-id="${dataAutomationId}"]`))[index]; unitTestingUtils.getAllByCSS(`[data-automation-id="${dataAutomationId}"]`)[index];
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -45,13 +45,14 @@ describe('RuleActionListUiComponent', () => {
fixture = TestBed.createComponent(RuleActionListUiComponent); fixture = TestBed.createComponent(RuleActionListUiComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
unitTestingUtils = new UnitTestingUtils(fixture.debugElement);
component.writeValue([]); component.writeValue([]);
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should default to 1 empty action when an empty array of actions is written', () => { it('should default to 1 empty action when an empty array of actions is written', () => {
const acaRuleActions = fixture.debugElement.queryAll(By.directive(RuleActionUiComponent)); const acaRuleActions = unitTestingUtils.getAllByDirective(RuleActionUiComponent);
expect(acaRuleActions.length).toBe(1); expect(acaRuleActions.length).toBe(1);
}); });
@@ -60,7 +61,7 @@ describe('RuleActionListUiComponent', () => {
addActionButton.click(); addActionButton.click();
fixture.detectChanges(); fixture.detectChanges();
const acaRuleActions = fixture.debugElement.queryAll(By.directive(RuleActionUiComponent)); const acaRuleActions = unitTestingUtils.getAllByDirective(RuleActionUiComponent);
expect(acaRuleActions.length).toBe(2); expect(acaRuleActions.length).toBe(2);
}); });
@@ -99,7 +100,7 @@ describe('RuleActionListUiComponent', () => {
removeActionButton.click(); removeActionButton.click();
fixture.detectChanges(); fixture.detectChanges();
const acaRuleActions = fixture.debugElement.queryAll(By.directive(RuleActionUiComponent)); const acaRuleActions = unitTestingUtils.getAllByDirective(RuleActionUiComponent);
expect(acaRuleActions.length).toBe(1); expect(acaRuleActions.length).toBe(1);
}); });
}); });

View File

@@ -23,7 +23,14 @@
*/ */
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CardViewBoolItemModel, CardViewComponent, CardViewSelectItemModel, CardViewTextItemModel, NoopTranslateModule } from '@alfresco/adf-core'; import {
CardViewBoolItemModel,
CardViewComponent,
CardViewSelectItemModel,
CardViewTextItemModel,
NoopTranslateModule,
UnitTestingUtils
} from '@alfresco/adf-core';
import { RuleActionUiComponent } from './rule-action.ui-component'; import { RuleActionUiComponent } from './rule-action.ui-component';
import { import {
actionLinkToCategoryTransformedMock, actionLinkToCategoryTransformedMock,
@@ -31,20 +38,35 @@ import {
actionsTransformedListMock, actionsTransformedListMock,
securityActionTransformedMock securityActionTransformedMock
} from '../../mock/actions.mock'; } from '../../mock/actions.mock';
import { By } from '@angular/platform-browser';
import { dummyCategoriesConstraints, dummyConstraints, dummyTagsConstraints } from '../../mock/action-parameter-constraints.mock'; import { dummyCategoriesConstraints, dummyConstraints, dummyTagsConstraints } from '../../mock/action-parameter-constraints.mock';
import { securityMarksResponseMock, updateNotificationMock } from '../../mock/security-marks.mock'; import { securityMarksResponseMock, updateNotificationMock } from '../../mock/security-marks.mock';
import { AlfrescoApiService, AlfrescoApiServiceMock, CategoryService, NodeAction, TagService } from '@alfresco/adf-content-services'; import {
import { MatDialog, MatDialogRef } from '@angular/material/dialog'; AlfrescoApiService,
AlfrescoApiServiceMock,
CategoryService,
ContentNodeSelectorComponent,
NodeAction,
SecurityControlsService,
TagService
} from '@alfresco/adf-content-services';
import { MatDialog, MatDialogConfig, MatDialogRef } from '@angular/material/dialog';
import { HarnessLoader } from '@angular/cdk/testing'; import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatSelectHarness } from '@angular/material/select/testing'; import { MatSelectHarness } from '@angular/material/select/testing';
import { of, Subject } from 'rxjs'; import { of, Subject } from 'rxjs';
import { Node } from '@alfresco/js-api';
import { SimpleChanges } from '@angular/core';
describe('RuleActionUiComponent', () => { describe('RuleActionUiComponent', () => {
let fixture: ComponentFixture<RuleActionUiComponent>; let fixture: ComponentFixture<RuleActionUiComponent>;
let component: RuleActionUiComponent; let component: RuleActionUiComponent;
let loader: HarnessLoader; let loader: HarnessLoader;
let unitTestingUtils: UnitTestingUtils;
let securityControlsService: SecurityControlsService;
const clickActionItem = () => {
unitTestingUtils.getByCSS('.adf-textitem-action').nativeElement.click();
};
const changeMatSelectValue = async (value: string) => { const changeMatSelectValue = async (value: string) => {
const matSelect = await loader.getHarness(MatSelectHarness); const matSelect = await loader.getHarness(MatSelectHarness);
@@ -52,14 +74,7 @@ describe('RuleActionUiComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
}; };
const getPropertiesCardView = (): CardViewComponent => fixture.debugElement.query(By.directive(CardViewComponent)).componentInstance; const getPropertiesCardView = (): CardViewComponent => unitTestingUtils.getByDirective(CardViewComponent).componentInstance;
function setInputValue(value: string) {
const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = value;
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
}
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -69,7 +84,9 @@ describe('RuleActionUiComponent', () => {
fixture = TestBed.createComponent(RuleActionUiComponent); fixture = TestBed.createComponent(RuleActionUiComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
securityControlsService = TestBed.inject(SecurityControlsService);
loader = TestbedHarnessEnvironment.loader(fixture); loader = TestbedHarnessEnvironment.loader(fixture);
unitTestingUtils = new UnitTestingUtils(fixture.debugElement, loader);
}); });
it('should not accept empty parameters', async () => { it('should not accept empty parameters', async () => {
@@ -79,12 +96,12 @@ describe('RuleActionUiComponent', () => {
await changeMatSelectValue('mock-action-1-definition'); await changeMatSelectValue('mock-action-1-definition');
setInputValue('test'); await unitTestingUtils.fillMatInput('test');
await fixture.whenStable(); await fixture.whenStable();
expect(component.parameters).toEqual({ 'mock-action-parameter-boolean': false, 'mock-action-parameter-text': 'test' }); expect(component.parameters).toEqual({ 'mock-action-parameter-boolean': false, 'mock-action-parameter-text': 'test' });
setInputValue(''); await unitTestingUtils.fillMatInput('');
await fixture.whenStable(); await fixture.whenStable();
expect(component.parameters).toEqual({ 'mock-action-parameter-boolean': false }); expect(component.parameters).toEqual({ 'mock-action-parameter-boolean': false });
@@ -119,7 +136,7 @@ describe('RuleActionUiComponent', () => {
expect(cardView.properties[4]).toBeInstanceOf(CardViewSelectItemModel); expect(cardView.properties[4]).toBeInstanceOf(CardViewSelectItemModel);
await changeMatSelectValue('mock-action-2-definition'); await changeMatSelectValue('mock-action-2-definition');
expect(fixture.debugElement.query(By.directive(CardViewComponent))).toBeNull(); expect(unitTestingUtils.getByDirective(CardViewComponent)).toBeNull();
}); });
it('should create category-value action parameter as a text box rather than node picker', async () => { it('should create category-value action parameter as a text box rather than node picker', async () => {
@@ -138,21 +155,21 @@ describe('RuleActionUiComponent', () => {
}); });
it('should open category selector dialog on category-value action parameter clicked', async () => { it('should open category selector dialog on category-value action parameter clicked', async () => {
const dialog = fixture.debugElement.injector.get(MatDialog); const dialog = TestBed.inject(MatDialog);
component.actionDefinitions = [actionLinkToCategoryTransformedMock]; component.actionDefinitions = [actionLinkToCategoryTransformedMock];
component.parameterConstraints = dummyConstraints; component.parameterConstraints = dummyConstraints;
spyOn(dialog, 'open'); spyOn(dialog, 'open');
fixture.detectChanges(); fixture.detectChanges();
await changeMatSelectValue('mock-action-3-definition'); await changeMatSelectValue('mock-action-3-definition');
fixture.debugElement.query(By.css('.adf-textitem-action')).nativeElement.click(); clickActionItem();
expect(dialog.open).toHaveBeenCalledTimes(1); expect(dialog.open).toHaveBeenCalledTimes(1);
expect(dialog.open['calls'].argsFor(0)[0].name).toBe('CategorySelectorDialogComponent'); expect(dialog.open['calls'].argsFor(0)[0].name).toBe('CategorySelectorDialogComponent');
}); });
it('should open node selector dialog with correct parameters', async () => { it('should open node selector dialog with correct parameters', async () => {
const dialog = fixture.debugElement.injector.get(MatDialog); const dialog = TestBed.inject(MatDialog);
component.actionDefinitions = [actionNodeTransformedMock]; component.actionDefinitions = [actionNodeTransformedMock];
const expectedData = { const expectedData = {
selectionMode: 'single', selectionMode: 'single',
@@ -165,7 +182,7 @@ describe('RuleActionUiComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
await changeMatSelectValue('mock-action-5-definition'); await changeMatSelectValue('mock-action-5-definition');
fixture.debugElement.query(By.css('.adf-textitem-action')).nativeElement.click(); clickActionItem();
expect(dialog.open).toHaveBeenCalledTimes(1); expect(dialog.open).toHaveBeenCalledTimes(1);
expect(dialogSpy.calls.mostRecent().args[1].data).toEqual(expectedData); expect(dialogSpy.calls.mostRecent().args[1].data).toEqual(expectedData);
@@ -248,6 +265,23 @@ describe('RuleActionUiComponent', () => {
]); ]);
}); });
}); });
it('should load security mark options when writeValue is called with securityGroupId parameter', async () => {
component.actionDefinitions = [securityActionTransformedMock];
spyOn(securityControlsService, 'getSecurityMark').and.returnValue(Promise.resolve(securityMarksResponseMock));
fixture.detectChanges();
await changeMatSelectValue('mock-action-4-definition');
component.writeValue({
actionDefinitionId: 'mock-action-4-definition',
params: { securityGroupId: 'group-1' }
});
await fixture.whenStable();
expect(securityControlsService.getSecurityMark).toHaveBeenCalled();
});
}); });
describe('Security mark actions', () => { describe('Security mark actions', () => {
@@ -280,4 +314,125 @@ describe('RuleActionUiComponent', () => {
}); });
}); });
}); });
describe('ContentNodeSelectorComponent dialog', () => {
let mockDialogRef: jasmine.SpyObj<MatDialogRef<ContentNodeSelectorComponent, Node[]>>;
let dialogOpenSpy: jasmine.Spy<
(component: typeof ContentNodeSelectorComponent, config?: MatDialogConfig) => MatDialogRef<ContentNodeSelectorComponent, Node[]>
>;
beforeEach(() => {
mockDialogRef = jasmine.createSpyObj('MatDialogRef', ['afterClosed']);
dialogOpenSpy = spyOn(TestBed.inject(MatDialog), 'open').and.returnValue(mockDialogRef);
spyOn(TestBed.inject(MatDialog), 'closeAll');
spyOn(console, 'error');
});
it('should open dialog when node selector is clicked', async () => {
component.actionDefinitions = [actionNodeTransformedMock];
component.nodeId = 'test-folder-id';
fixture.detectChanges();
await changeMatSelectValue('mock-action-5-definition');
clickActionItem();
expect(dialogOpenSpy).toHaveBeenCalledWith(ContentNodeSelectorComponent, {
data: {
selectionMode: 'single',
title: 'ACA_FOLDER_RULES.RULE_DETAILS.PLACEHOLDER.CHOOSE_FOLDER',
actionName: NodeAction.CHOOSE,
currentFolderId: 'test-folder-id',
select: jasmine.any(Subject)
},
panelClass: 'adf-content-node-selector-dialog',
width: '630px'
});
});
it('should update component when valid node is selected through dialog', async () => {
component.actionDefinitions = [actionNodeTransformedMock];
fixture.detectChanges();
await changeMatSelectValue('mock-action-5-definition');
component.parameters = { existingParam: 'value' };
clickActionItem();
const dialogData = dialogOpenSpy.calls.mostRecent().args[1].data;
const selectedNode = { id: 'selected-node-123', name: 'Test Folder' } as Node;
dialogData.select.next([selectedNode]);
expect(component.parameters).toEqual({
existingParam: 'value',
'aspect-name': 'selected-node-123'
});
});
it('should log error when dialog selection encounters error', async () => {
component.actionDefinitions = [actionNodeTransformedMock];
fixture.detectChanges();
await changeMatSelectValue('mock-action-5-definition');
clickActionItem();
const dialogData = dialogOpenSpy.calls.mostRecent().args[1].data;
const testError = new Error('Selection failed');
dialogData.select.error(testError);
expect(console.error).toHaveBeenCalledWith(testError);
});
it('should close all dialogs when selection completes', async () => {
const dialogService = TestBed.inject(MatDialog);
component.actionDefinitions = [actionNodeTransformedMock];
fixture.detectChanges();
await changeMatSelectValue('mock-action-5-definition');
clickActionItem();
const dialogData = dialogOpenSpy.calls.mostRecent().args[1].data;
dialogData.select.complete();
expect(dialogService.closeAll).toHaveBeenCalled();
});
});
it('should enable form when readOnly changes from true to false', () => {
component.readOnly = true;
component.form.disable();
const changes: SimpleChanges = {
readOnly: {
currentValue: false,
previousValue: true,
firstChange: false,
isFirstChange: () => false
}
};
component.ngOnChanges(changes);
expect(component.readOnly).toBe(false);
expect(component.form.enabled).toBe(true);
});
it('should disable form when readOnly changes from false to true', () => {
component.readOnly = false;
component.form.enable();
const changes: SimpleChanges = {
readOnly: {
currentValue: true,
previousValue: false,
firstChange: false,
isFirstChange: () => false
}
};
component.ngOnChanges(changes);
expect(component.readOnly).toBe(true);
expect(component.form.disabled).toBe(true);
});
}); });

View File

@@ -24,8 +24,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RuleCompositeConditionUiComponent } from './rule-composite-condition.ui-component'; import { RuleCompositeConditionUiComponent } from './rule-composite-condition.ui-component';
import { NoopTranslateModule } from '@alfresco/adf-core'; import { NoopTranslateModule, UnitTestingUtils } from '@alfresco/adf-core';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core'; import { DebugElement } from '@angular/core';
import { import {
compositeConditionWithNestedGroupsMock, compositeConditionWithNestedGroupsMock,
@@ -37,6 +36,10 @@ import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-conten
describe('RuleCompositeConditionUiComponent', () => { describe('RuleCompositeConditionUiComponent', () => {
let fixture: ComponentFixture<RuleCompositeConditionUiComponent>; let fixture: ComponentFixture<RuleCompositeConditionUiComponent>;
let unitTestingUtils: UnitTestingUtils;
const getSimpleConditionComponents = (): DebugElement[] => unitTestingUtils.getAllByCSS(`.aca-rule-simple-condition`);
const getCompositeConditionComponents = (): DebugElement[] => unitTestingUtils.getAllByCSS(`.aca-rule-composite-condition`);
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -45,18 +48,18 @@ describe('RuleCompositeConditionUiComponent', () => {
}); });
fixture = TestBed.createComponent(RuleCompositeConditionUiComponent); fixture = TestBed.createComponent(RuleCompositeConditionUiComponent);
unitTestingUtils = new UnitTestingUtils(fixture.debugElement);
}); });
describe('No conditions', () => { describe('No conditions', () => {
let noConditionsElement: DebugElement; const getNoConditionsElementInnerText = (): string => unitTestingUtils.getInnerTextByDataAutomationId('no-conditions');
beforeEach(() => { beforeEach(() => {
fixture.detectChanges(); fixture.detectChanges();
noConditionsElement = fixture.debugElement.query(By.css(`[data-automation-id="no-conditions"]`));
}); });
it('should default to no conditions', () => { it('should default to no conditions', () => {
const rowElements = fixture.debugElement.queryAll(By.css(`.aca-rule-composite-condition__form__row`)); const rowElements = unitTestingUtils.getAllByCSS(`.aca-rule-composite-condition__form__row`);
expect(rowElements.length).toBe(0); expect(rowElements.length).toBe(0);
}); });
@@ -65,14 +68,14 @@ describe('RuleCompositeConditionUiComponent', () => {
fixture.componentInstance.childCondition = false; fixture.componentInstance.childCondition = false;
fixture.detectChanges(); fixture.detectChanges();
expect((noConditionsElement.nativeElement as HTMLElement).innerText.trim()).toBe('ACA_FOLDER_RULES.RULE_DETAILS.NO_CONDITIONS'); expect(getNoConditionsElementInnerText()).toBe('ACA_FOLDER_RULES.RULE_DETAILS.NO_CONDITIONS');
}); });
it('should show a different message if the component is not a root condition group', () => { it('should show a different message if the component is not a root condition group', () => {
fixture.componentInstance.childCondition = true; fixture.componentInstance.childCondition = true;
fixture.detectChanges(); fixture.detectChanges();
expect((noConditionsElement.nativeElement as HTMLElement).innerText.trim()).toBe('ACA_FOLDER_RULES.RULE_DETAILS.NO_CONDITIONS_IN_GROUP'); expect(getNoConditionsElementInnerText()).toBe('ACA_FOLDER_RULES.RULE_DETAILS.NO_CONDITIONS_IN_GROUP');
}); });
}); });
@@ -80,64 +83,52 @@ describe('RuleCompositeConditionUiComponent', () => {
it('should hide the add buttons in read only mode', () => { it('should hide the add buttons in read only mode', () => {
fixture.componentInstance.readOnly = true; fixture.componentInstance.readOnly = true;
fixture.detectChanges(); fixture.detectChanges();
const actionsElement = fixture.debugElement.query(By.css(`[data-automation-id="add-actions"]`));
expect(actionsElement).toBeNull(); expect(unitTestingUtils.getByDataAutomationId('add-actions')).toBeNull();
}); });
it('should hide the more actions button on the right side of the condition', () => { it('should hide the more actions button on the right side of the condition', () => {
fixture.componentInstance.writeValue(compositeConditionWithOneGroupMock); fixture.componentInstance.writeValue(compositeConditionWithOneGroupMock);
fixture.componentInstance.readOnly = true; fixture.componentInstance.readOnly = true;
fixture.detectChanges(); fixture.detectChanges();
const actionsButtonElements = fixture.debugElement.queryAll(By.css(`[data-automation-id="condition-actions-button"]`));
expect(actionsButtonElements.length).toBe(0); expect(unitTestingUtils.getAllByCSS(`[data-automation-id="condition-actions-button"]`).length).toBe(0);
}); });
}); });
it('should have as many simple condition components as defined in the simpleConditions array', () => { it('should have as many simple condition components as defined in the simpleConditions array', () => {
fixture.componentInstance.writeValue(compositeConditionWithThreeConditionMock); fixture.componentInstance.writeValue(compositeConditionWithThreeConditionMock);
fixture.detectChanges(); fixture.detectChanges();
const simpleConditionComponents = fixture.debugElement.queryAll(By.css(`.aca-rule-simple-condition`));
expect(simpleConditionComponents.length).toBe(3); expect(getSimpleConditionComponents().length).toBe(3);
}); });
it('should have as many composite condition components as defined in the compositeConditions array, including nested', () => { it('should have as many composite condition components as defined in the compositeConditions array, including nested', () => {
fixture.componentInstance.writeValue(compositeConditionWithNestedGroupsMock); fixture.componentInstance.writeValue(compositeConditionWithNestedGroupsMock);
fixture.detectChanges(); fixture.detectChanges();
const compositeConditionComponents = fixture.debugElement.queryAll(By.css(`.aca-rule-composite-condition`));
expect(compositeConditionComponents.length).toBe(3); expect(getCompositeConditionComponents().length).toBe(3);
}); });
it('should add a simple condition component on click of add condition button', () => { it('should add a simple condition component on click of add condition button', () => {
fixture.detectChanges(); fixture.detectChanges();
const predicate = By.css(`.aca-rule-simple-condition`);
const simpleConditionComponentsBeforeClick = fixture.debugElement.queryAll(predicate);
expect(simpleConditionComponentsBeforeClick.length).toBe(0); expect(getSimpleConditionComponents().length).toBe(0);
const addConditionButton = fixture.debugElement.query(By.css(`[data-automation-id="add-condition-button"]`)); unitTestingUtils.clickByDataAutomationId('add-condition-button');
(addConditionButton.nativeElement as HTMLButtonElement).click();
fixture.detectChanges(); fixture.detectChanges();
const simpleConditionComponentsAfterClick = fixture.debugElement.queryAll(predicate);
expect(simpleConditionComponentsAfterClick.length).toBe(1); expect(getSimpleConditionComponents().length).toBe(1);
}); });
it('should add a composite condition component on click of add group button', () => { it('should add a composite condition component on click of add group button', () => {
fixture.detectChanges(); fixture.detectChanges();
const predicate = By.css(`.aca-rule-composite-condition`);
const compositeConditionComponentsBeforeClick = fixture.debugElement.queryAll(predicate);
expect(compositeConditionComponentsBeforeClick.length).toBe(0); expect(getCompositeConditionComponents().length).toBe(0);
const addGroupButton = fixture.debugElement.query(By.css(`[data-automation-id="add-group-button"]`)); unitTestingUtils.clickByDataAutomationId('add-group-button');
(addGroupButton.nativeElement as HTMLButtonElement).click();
fixture.detectChanges(); fixture.detectChanges();
const compositeConditionComponentsAfterClick = fixture.debugElement.queryAll(predicate);
expect(compositeConditionComponentsAfterClick.length).toBe(1); expect(getCompositeConditionComponents().length).toBe(1);
}); });
}); });

View File

@@ -24,8 +24,6 @@
import { ComponentFixture, discardPeriodicTasks, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { ComponentFixture, discardPeriodicTasks, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { RuleSimpleConditionUiComponent } from './rule-simple-condition.ui-component'; import { RuleSimpleConditionUiComponent } from './rule-simple-condition.ui-component';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { tagMock, mimeTypeMock, simpleConditionUnknownFieldMock, categoriesListMock } from '../../mock/conditions.mock'; import { tagMock, mimeTypeMock, simpleConditionUnknownFieldMock, categoriesListMock } from '../../mock/conditions.mock';
import { AlfrescoApiService, AlfrescoApiServiceMock, CategoryService, TagService } from '@alfresco/adf-content-services'; import { AlfrescoApiService, AlfrescoApiServiceMock, CategoryService, TagService } from '@alfresco/adf-content-services';
import { of } from 'rxjs'; import { of } from 'rxjs';
@@ -37,19 +35,24 @@ import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatSelectHarness } from '@angular/material/select/testing'; import { MatSelectHarness } from '@angular/material/select/testing';
import { MatAutocompleteHarness } from '@angular/material/autocomplete/testing'; import { MatAutocompleteHarness } from '@angular/material/autocomplete/testing';
import { AlfrescoMimeType } from '@alfresco/aca-shared'; import { AlfrescoMimeType } from '@alfresco/aca-shared';
import { NoopTranslateModule } from '@alfresco/adf-core'; import { NoopTranslateModule, UnitTestingUtils } from '@alfresco/adf-core';
describe('RuleSimpleConditionUiComponent', () => { describe('RuleSimpleConditionUiComponent', () => {
let fixture: ComponentFixture<RuleSimpleConditionUiComponent>; let fixture: ComponentFixture<RuleSimpleConditionUiComponent>;
let categoryService: CategoryService; let categoryService: CategoryService;
let loader: HarnessLoader; let loader: HarnessLoader;
let unitTestingUtils: UnitTestingUtils;
const fieldSelectAutomationId = 'field-select'; const fieldSelectAutomationId = 'field-select';
const comparatorSelectAutomationId = 'comparator-select';
const comparatorFormFieldAutomationId = 'comparator-form-field';
const unknownFieldOptionAutomationId = 'unknown-field-option';
const simpleConditionValueAutomationId = 'simple-condition-value-select';
const autoCompleteInputFieldAutomationId = 'auto-complete-input-field';
const autoCompleteSpinnerAutomationId = 'auto-complete-loading-spinner';
const valueInputAutomationId = 'value-input';
const folderRulesBaseLabel = 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS'; const folderRulesBaseLabel = 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS';
const getByDataAutomationId = (dataAutomationId: string): DebugElement =>
fixture.debugElement.query(By.css(`[data-automation-id="${dataAutomationId}"]`));
const changeMatSelectValue = async (dataAutomationId: string, value: string) => { const changeMatSelectValue = async (dataAutomationId: string, value: string) => {
const matSelect = await loader.getHarness(MatSelectHarness.with({ selector: `[data-automation-id="${dataAutomationId}"]` })); const matSelect = await loader.getHarness(MatSelectHarness.with({ selector: `[data-automation-id="${dataAutomationId}"]` }));
await matSelect.clickOptions({ selector: `[ng-reflect-value="${value}"]` }); await matSelect.clickOptions({ selector: `[ng-reflect-value="${value}"]` });
@@ -62,13 +65,6 @@ describe('RuleSimpleConditionUiComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
}; };
const setValueInInputField = (inputFieldDataAutomationId: string, value: string) => {
const inputField = fixture.debugElement.query(By.css(`[data-automation-id="${inputFieldDataAutomationId}"]`)).nativeElement;
inputField.value = value;
inputField.dispatchEvent(new Event('input'));
fixture.detectChanges();
};
const expectConditionFieldsDisplayedAsOptions = async (conditionFields: RuleConditionField[]): Promise<void> => { const expectConditionFieldsDisplayedAsOptions = async (conditionFields: RuleConditionField[]): Promise<void> => {
loader = TestbedHarnessEnvironment.loader(fixture); loader = TestbedHarnessEnvironment.loader(fixture);
const select = await loader.getHarness(MatSelectHarness); const select = await loader.getHarness(MatSelectHarness);
@@ -90,20 +86,21 @@ describe('RuleSimpleConditionUiComponent', () => {
fixture = TestBed.createComponent(RuleSimpleConditionUiComponent); fixture = TestBed.createComponent(RuleSimpleConditionUiComponent);
categoryService = TestBed.inject(CategoryService); categoryService = TestBed.inject(CategoryService);
loader = TestbedHarnessEnvironment.loader(fixture); loader = TestbedHarnessEnvironment.loader(fixture);
unitTestingUtils = new UnitTestingUtils(fixture.debugElement, loader);
}); });
it('should default the field to the name, the comparator to equals and the value empty', () => { it('should default the field to the name, the comparator to equals and the value empty', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(getByDataAutomationId(fieldSelectAutomationId).componentInstance.value).toBe('cm:name'); expect(unitTestingUtils.getByDataAutomationId(fieldSelectAutomationId).componentInstance.value).toBe('cm:name');
expect(getByDataAutomationId('comparator-select').componentInstance.value).toBe('equals'); expect(unitTestingUtils.getByDataAutomationId(comparatorSelectAutomationId).componentInstance.value).toBe('equals');
expect(getByDataAutomationId('value-input').nativeElement.value).toBe(''); expect(unitTestingUtils.getByDataAutomationId(valueInputAutomationId).nativeElement.value).toBe('');
}); });
it('should hide the comparator select box if the type of the field is mimeType', async () => { it('should hide the comparator select box if the type of the field is mimeType', async () => {
fixture.componentInstance.mimeTypes = [{ value: '', label: '' } as AlfrescoMimeType]; fixture.componentInstance.mimeTypes = [{ value: '', label: '' } as AlfrescoMimeType];
fixture.detectChanges(); fixture.detectChanges();
const comparatorFormField = getByDataAutomationId('comparator-form-field').nativeElement; const comparatorFormField = unitTestingUtils.getByDataAutomationId(comparatorFormFieldAutomationId).nativeElement;
expect(fixture.componentInstance.isComparatorHidden).toBeFalsy(); expect(fixture.componentInstance.isComparatorHidden).toBeFalsy();
expect(getComputedStyle(comparatorFormField).display).not.toBe('none'); expect(getComputedStyle(comparatorFormField).display).not.toBe('none');
@@ -116,9 +113,9 @@ describe('RuleSimpleConditionUiComponent', () => {
it('should set the comparator to equals if the field is set to a type with different comparators', async () => { it('should set the comparator to equals if the field is set to a type with different comparators', async () => {
spyOn(fixture.componentInstance, 'onChangeField').and.callThrough(); spyOn(fixture.componentInstance, 'onChangeField').and.callThrough();
const comparatorSelect = await loader.getHarness(MatSelectHarness.with({ selector: '[data-automation-id="comparator-select"]' })); const comparatorSelect = await loader.getHarness(MatSelectHarness.with({ selector: `[data-automation-id="${comparatorSelectAutomationId}"]` }));
await changeMatSelectValue('comparator-select', 'contains'); await changeMatSelectValue(comparatorSelectAutomationId, 'contains');
expect(await comparatorSelect.getValueText()).toBe(folderRulesBaseLabel + '.CONTAINS'); expect(await comparatorSelect.getValueText()).toBe(folderRulesBaseLabel + '.CONTAINS');
await changeMatSelectValue(fieldSelectAutomationId, 'size'); await changeMatSelectValue(fieldSelectAutomationId, 'size');
@@ -130,25 +127,23 @@ describe('RuleSimpleConditionUiComponent', () => {
fixture.componentInstance.writeValue(simpleConditionUnknownFieldMock); fixture.componentInstance.writeValue(simpleConditionUnknownFieldMock);
fixture.detectChanges(); fixture.detectChanges();
expect(getByDataAutomationId(fieldSelectAutomationId).componentInstance.value).toBe(simpleConditionUnknownFieldMock.field); expect(unitTestingUtils.getByDataAutomationId(fieldSelectAutomationId).componentInstance.value).toBe(simpleConditionUnknownFieldMock.field);
const matSelect = getByDataAutomationId(fieldSelectAutomationId).nativeElement; const matSelect = unitTestingUtils.getByDataAutomationId(fieldSelectAutomationId).nativeElement;
matSelect.click(); matSelect.click();
fixture.detectChanges(); fixture.detectChanges();
const unknownOptionMatOption = getByDataAutomationId('unknown-field-option'); expect(unitTestingUtils.getInnerTextByDataAutomationId(unknownFieldOptionAutomationId).trim()).toBe(simpleConditionUnknownFieldMock.field);
expect(unknownOptionMatOption).not.toBeNull();
expect((unknownOptionMatOption.nativeElement as HTMLElement).innerText.trim()).toBe(simpleConditionUnknownFieldMock.field);
}); });
it('should remove the option for the unknown field as soon as another option is selected', async () => { it('should remove the option for the unknown field as soon as another option is selected', async () => {
fixture.componentInstance.writeValue(simpleConditionUnknownFieldMock); fixture.componentInstance.writeValue(simpleConditionUnknownFieldMock);
fixture.detectChanges(); fixture.detectChanges();
await changeMatSelectValue(fieldSelectAutomationId, 'cm:name'); await changeMatSelectValue(fieldSelectAutomationId, 'cm:name');
const matSelect = getByDataAutomationId(fieldSelectAutomationId).nativeElement; const matSelect = unitTestingUtils.getByDataAutomationId(fieldSelectAutomationId).nativeElement;
matSelect.click(); matSelect.click();
fixture.detectChanges(); fixture.detectChanges();
const unknownOptionMatOption = getByDataAutomationId('unknown-field-option'); const unknownOptionMatOption = unitTestingUtils.getByDataAutomationId(unknownFieldOptionAutomationId);
expect(unknownOptionMatOption).toBeNull(); expect(unknownOptionMatOption).toBeNull();
}); });
@@ -178,7 +173,7 @@ describe('RuleSimpleConditionUiComponent', () => {
fixture.componentInstance.onChangeField(); fixture.componentInstance.onChangeField();
fixture.detectChanges(); fixture.detectChanges();
expect(getByDataAutomationId('simple-condition-value-select')).toBeTruthy(); expect(unitTestingUtils.getByDataAutomationId(simpleConditionValueAutomationId)).toBeTruthy();
expect(fixture.componentInstance.form.get('parameter').value).toEqual(mockMimeTypes[0].value); expect(fixture.componentInstance.form.get('parameter').value).toEqual(mockMimeTypes[0].value);
}); });
@@ -186,12 +181,12 @@ describe('RuleSimpleConditionUiComponent', () => {
fixture.componentInstance.writeValue(mimeTypeMock); fixture.componentInstance.writeValue(mimeTypeMock);
fixture.detectChanges(); fixture.detectChanges();
expect(getByDataAutomationId('simple-condition-value-select')).toBeTruthy(); expect(unitTestingUtils.getByDataAutomationId(simpleConditionValueAutomationId)).toBeTruthy();
fixture.componentInstance.writeValue(tagMock); fixture.componentInstance.writeValue(tagMock);
fixture.detectChanges(); fixture.detectChanges();
expect(getByDataAutomationId('value-input').nativeElement.value).toBe(''); expect(unitTestingUtils.getByDataAutomationId(valueInputAutomationId).nativeElement.value).toBe('');
}); });
it('should show loading spinner while auto-complete options are fetched, and then remove it once it is received', fakeAsync(async () => { it('should show loading spinner while auto-complete options are fetched, and then remove it once it is received', fakeAsync(async () => {
@@ -199,12 +194,12 @@ describe('RuleSimpleConditionUiComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
await changeMatSelectValue(fieldSelectAutomationId, 'category'); await changeMatSelectValue(fieldSelectAutomationId, 'category');
tick(500); tick(500);
getByDataAutomationId('auto-complete-input-field')?.nativeElement?.click(); unitTestingUtils.getByDataAutomationId(autoCompleteInputFieldAutomationId)?.nativeElement?.click();
let loadingSpinner = getByDataAutomationId('auto-complete-loading-spinner'); let loadingSpinner = unitTestingUtils.getByDataAutomationId(autoCompleteSpinnerAutomationId);
expect(loadingSpinner).not.toBeNull(); expect(loadingSpinner).not.toBeNull();
tick(1000); tick(1000);
fixture.detectChanges(); fixture.detectChanges();
loadingSpinner = getByDataAutomationId('auto-complete-loading-spinner'); loadingSpinner = unitTestingUtils.getByDataAutomationId(autoCompleteSpinnerAutomationId);
expect(loadingSpinner).toBeNull(); expect(loadingSpinner).toBeNull();
discardPeriodicTasks(); discardPeriodicTasks();
})); }));
@@ -217,7 +212,7 @@ describe('RuleSimpleConditionUiComponent', () => {
it('should hide the comparator select box if the type of the field is autoComplete', async () => { it('should hide the comparator select box if the type of the field is autoComplete', async () => {
const autoCompleteField = 'category'; const autoCompleteField = 'category';
fixture.detectChanges(); fixture.detectChanges();
const comparatorFormField = getByDataAutomationId('comparator-form-field').nativeElement; const comparatorFormField = unitTestingUtils.getByDataAutomationId(comparatorFormFieldAutomationId).nativeElement;
expect(fixture.componentInstance.isComparatorHidden).toBeFalsy(); expect(fixture.componentInstance.isComparatorHidden).toBeFalsy();
expect(getComputedStyle(comparatorFormField).display).not.toBe('none'); expect(getComputedStyle(comparatorFormField).display).not.toBe('none');
@@ -230,7 +225,7 @@ describe('RuleSimpleConditionUiComponent', () => {
it('should hide the comparator select box if the type of the field is special', async () => { it('should hide the comparator select box if the type of the field is special', async () => {
fixture.detectChanges(); fixture.detectChanges();
const comparatorFormField = getByDataAutomationId('comparator-form-field').nativeElement; const comparatorFormField = unitTestingUtils.getByDataAutomationId(comparatorFormFieldAutomationId).nativeElement;
expect(fixture.componentInstance.isComparatorHidden).toBeFalsy(); expect(fixture.componentInstance.isComparatorHidden).toBeFalsy();
expect(getComputedStyle(comparatorFormField).display).not.toBe('none'); expect(getComputedStyle(comparatorFormField).display).not.toBe('none');
@@ -245,7 +240,7 @@ describe('RuleSimpleConditionUiComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
await changeMatSelectValue(fieldSelectAutomationId, 'category'); await changeMatSelectValue(fieldSelectAutomationId, 'category');
expect(getByDataAutomationId('auto-complete-input-field')).toBeTruthy(); expect(unitTestingUtils.getByDataAutomationId(autoCompleteInputFieldAutomationId)).toBeTruthy();
expect(fixture.componentInstance.form.get('parameter').value).toEqual(''); expect(fixture.componentInstance.form.get('parameter').value).toEqual('');
}); });
@@ -265,7 +260,7 @@ describe('RuleSimpleConditionUiComponent', () => {
tick(500); tick(500);
expect(categoryService.searchCategories).toHaveBeenCalledWith(''); expect(categoryService.searchCategories).toHaveBeenCalledWith('');
setValueInInputField('auto-complete-input-field', categoryValue); unitTestingUtils.fillInputByDataAutomationId(autoCompleteInputFieldAutomationId, categoryValue);
tick(500); tick(500);
expect(categoryService.searchCategories).toHaveBeenCalledWith(categoryValue); expect(categoryService.searchCategories).toHaveBeenCalledWith(categoryValue);
})); }));
@@ -274,9 +269,9 @@ describe('RuleSimpleConditionUiComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
await changeMatSelectValue(fieldSelectAutomationId, 'category'); await changeMatSelectValue(fieldSelectAutomationId, 'category');
tick(500); tick(500);
getByDataAutomationId('auto-complete-input-field')?.nativeElement?.click(); unitTestingUtils.getByDataAutomationId(autoCompleteInputFieldAutomationId)?.nativeElement?.click();
await changeMatAutocompleteValue(categoriesListMock.list.entries[0].entry.id); await changeMatAutocompleteValue(categoriesListMock.list.entries[0].entry.id);
const displayValue = getByDataAutomationId('auto-complete-input-field')?.nativeElement?.value; const displayValue = unitTestingUtils.getByDataAutomationId(autoCompleteInputFieldAutomationId)?.nativeElement?.value;
expect(displayValue).toBe('category/path/1/FakeCategory1'); expect(displayValue).toBe('category/path/1/FakeCategory1');
discardPeriodicTasks(); discardPeriodicTasks();
})); }));
@@ -285,7 +280,7 @@ describe('RuleSimpleConditionUiComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
await changeMatSelectValue(fieldSelectAutomationId, 'category'); await changeMatSelectValue(fieldSelectAutomationId, 'category');
tick(500); tick(500);
const autoCompleteInputField = getByDataAutomationId('auto-complete-input-field')?.nativeElement; const autoCompleteInputField = unitTestingUtils.getByDataAutomationId(autoCompleteInputFieldAutomationId)?.nativeElement;
autoCompleteInputField.value = 'FakeCat'; autoCompleteInputField.value = 'FakeCat';
autoCompleteInputField.dispatchEvent(new Event('focusout')); autoCompleteInputField.dispatchEvent(new Event('focusout'));
const parameterValue = fixture.componentInstance.form.get('parameter').value; const parameterValue = fixture.componentInstance.form.get('parameter').value;

View File

@@ -24,21 +24,26 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EditRuleDialogOptions, EditRuleDialogUiComponent } from './edit-rule-dialog.ui-component'; import { EditRuleDialogOptions, EditRuleDialogUiComponent } from './edit-rule-dialog.ui-component';
import { By } from '@angular/platform-browser';
import { RuleDetailsUiComponent } from './rule-details.ui-component'; import { RuleDetailsUiComponent } from './rule-details.ui-component';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { NoopTranslateModule } from '@alfresco/adf-core'; import { NoopTranslateModule, UnitTestingUtils } from '@alfresco/adf-core';
import { of, timer } from 'rxjs'; import { of, timer } from 'rxjs';
import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-content-services'; import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-content-services';
import { Rule } from '../model/rule.model';
describe('EditRuleDialogSmartComponent', () => { describe('EditRuleDialogSmartComponent', () => {
let fixture: ComponentFixture<EditRuleDialogUiComponent>; let fixture: ComponentFixture<EditRuleDialogUiComponent>;
let component: EditRuleDialogUiComponent;
let unitTestingUtils: UnitTestingUtils;
const dialogRef = { const dialogRef = {
close: jasmine.createSpy('close'), close: jasmine.createSpy('close'),
open: jasmine.createSpy('open') open: jasmine.createSpy('open')
}; };
const getDialogSubmit = (): HTMLButtonElement => unitTestingUtils.getByDataAutomationId('edit-rule-dialog-submit').nativeElement;
const getDialogTitle = (): HTMLDivElement => unitTestingUtils.getByDataAutomationId('edit-rule-dialog-title').nativeElement;
const setupBeforeEach = (dialogOptions: EditRuleDialogOptions = { actionDefinitions$: of([]), parameterConstraints$: of([]) }) => { const setupBeforeEach = (dialogOptions: EditRuleDialogOptions = { actionDefinitions$: of([]), parameterConstraints$: of([]) }) => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [NoopTranslateModule, EditRuleDialogUiComponent], imports: [NoopTranslateModule, EditRuleDialogUiComponent],
@@ -51,6 +56,8 @@ describe('EditRuleDialogSmartComponent', () => {
fixture = TestBed.createComponent(EditRuleDialogUiComponent); fixture = TestBed.createComponent(EditRuleDialogUiComponent);
fixture.detectChanges(); fixture.detectChanges();
component = fixture.componentInstance;
unitTestingUtils = new UnitTestingUtils(fixture.debugElement);
}; };
describe('No dialog options passed / indifferent', () => { describe('No dialog options passed / indifferent', () => {
@@ -59,38 +66,33 @@ describe('EditRuleDialogSmartComponent', () => {
}); });
it('should activate the submit button only when a valid state is received', async () => { it('should activate the submit button only when a valid state is received', async () => {
const submitButton = fixture.debugElement.query(By.css('[data-automation-id="edit-rule-dialog-submit"]')).nativeElement as HTMLButtonElement; const ruleDetails = unitTestingUtils.getByDirective(RuleDetailsUiComponent).componentInstance as RuleDetailsUiComponent;
const ruleDetails = fixture.debugElement.query(By.directive(RuleDetailsUiComponent)).componentInstance as RuleDetailsUiComponent;
ruleDetails.formValidationChanged.emit(true); ruleDetails.formValidationChanged.emit(true);
fixture.detectChanges(); fixture.detectChanges();
// timer needed to wait for the next tick to avoid ExpressionChangedAfterItHasBeenCheckedError // timer needed to wait for the next tick to avoid ExpressionChangedAfterItHasBeenCheckedError
await timer(1).toPromise(); await timer(1).toPromise();
fixture.detectChanges(); fixture.detectChanges();
expect(submitButton.disabled).toBeFalsy(); expect(getDialogSubmit().disabled).toBeFalsy();
ruleDetails.formValidationChanged.emit(false); ruleDetails.formValidationChanged.emit(false);
fixture.detectChanges(); fixture.detectChanges();
await timer(1).toPromise(); await timer(1).toPromise();
fixture.detectChanges(); fixture.detectChanges();
expect(submitButton.disabled).toBeTruthy(); expect(getDialogSubmit().disabled).toBeTruthy();
}); });
it('should show a "create" label in the title', () => { it('should show a "create" label in the title', () => {
const titleElement = fixture.debugElement.query(By.css('[data-automation-id="edit-rule-dialog-title"]')).nativeElement as HTMLDivElement; expect(getDialogTitle().innerText.trim()).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.CREATE_TITLE');
expect(titleElement.innerText.trim()).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.CREATE_TITLE');
}); });
it('should show a "create" label in the submit button', () => { it('should show a "create" label in the submit button', () => {
const titleElement = fixture.debugElement.query(By.css('[data-automation-id="edit-rule-dialog-submit"]')).nativeElement as HTMLButtonElement; expect(getDialogSubmit().innerText.trim()).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.CREATE');
expect(titleElement.innerText.trim()).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.CREATE');
}); });
it('should set correct title and submitLabel for create mode', () => { it('should set correct title and submitLabel for create mode', () => {
expect(fixture.componentInstance.title).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.CREATE_TITLE'); expect(component.title).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.CREATE_TITLE');
expect(fixture.componentInstance.submitLabel).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.CREATE'); expect(component.submitLabel).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.CREATE');
}); });
}); });
@@ -108,20 +110,36 @@ describe('EditRuleDialogSmartComponent', () => {
}); });
it('should show an "update" label in the title', () => { it('should show an "update" label in the title', () => {
const titleElement = fixture.debugElement.query(By.css('[data-automation-id="edit-rule-dialog-title"]')).nativeElement as HTMLDivElement; expect(getDialogTitle().innerText.trim()).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.UPDATE_TITLE');
expect(titleElement.innerText.trim()).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.UPDATE_TITLE');
}); });
it('should show an "update" label in the submit button', () => { it('should show an "update" label in the submit button', () => {
const titleElement = fixture.debugElement.query(By.css('[data-automation-id="edit-rule-dialog-submit"]')).nativeElement as HTMLButtonElement; expect(getDialogSubmit().innerText.trim()).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.UPDATE');
expect(titleElement.innerText.trim()).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.UPDATE');
}); });
it('should set correct title and submitLabel for update mode', () => { it('should set correct title and submitLabel for update mode', () => {
expect(fixture.componentInstance.title).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.UPDATE_TITLE'); expect(component.title).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.UPDATE_TITLE');
expect(fixture.componentInstance.submitLabel).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.UPDATE'); expect(component.submitLabel).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.UPDATE');
}); });
}); });
it('should emit submitted event with form value when onSubmit is called', () => {
setupBeforeEach();
let emittedValue: Partial<Rule> | undefined;
const testFormValue: Partial<Rule> = {
id: 'test-id',
name: 'Test Rule',
description: 'Test description'
};
component.submitted.subscribe((value) => {
emittedValue = value;
});
component.formValue = testFormValue;
component.onSubmit();
expect(emittedValue).toEqual(testFormValue);
});
}); });

View File

@@ -25,8 +25,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DebugElement, SimpleChange } from '@angular/core'; import { DebugElement, SimpleChange } from '@angular/core';
import { RuleOptionsUiComponent } from './rule-options.ui-component'; import { RuleOptionsUiComponent } from './rule-options.ui-component';
import { NoopTranslateModule } from '@alfresco/adf-core'; import { NoopTranslateModule, UnitTestingUtils } from '@alfresco/adf-core';
import { By } from '@angular/platform-browser';
import { errorScriptConstraintMock } from '../../mock/actions.mock'; import { errorScriptConstraintMock } from '../../mock/actions.mock';
import { HarnessLoader } from '@angular/cdk/testing'; import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
@@ -37,23 +36,27 @@ describe('RuleOptionsUiComponent', () => {
let fixture: ComponentFixture<RuleOptionsUiComponent>; let fixture: ComponentFixture<RuleOptionsUiComponent>;
let component: RuleOptionsUiComponent; let component: RuleOptionsUiComponent;
let loader: HarnessLoader; let loader: HarnessLoader;
let unitTestingUtils: UnitTestingUtils;
const getByDataAutomationId = (dataAutomationId: string): DebugElement => const errorScriptSelectAutomationId = 'rule-option-select-errorScript';
fixture.debugElement.query(By.css(`[data-automation-id="${dataAutomationId}"]`)); const asynchronousCheckboxAutomationId = 'rule-option-checkbox-asynchronous';
const getErrorScriptSelect = (): DebugElement => unitTestingUtils.getByDataAutomationId(errorScriptSelectAutomationId);
const getAsynchronousCheckbox = (): DebugElement => unitTestingUtils.getByDataAutomationId(asynchronousCheckboxAutomationId);
const getErrorScriptFormField = (): DebugElement => unitTestingUtils.getByDataAutomationId('rule-option-form-field-errorScript');
const getInheritableCheckbox = (): DebugElement => unitTestingUtils.getByDataAutomationId('rule-option-checkbox-inheritable');
const getDisabledCheckbox = (): DebugElement => unitTestingUtils.getByDataAutomationId('rule-option-checkbox-disabled');
const getEnabledCheckbox = (): DebugElement => unitTestingUtils.getByDataAutomationId('rule-option-checkbox-enabled');
const toggleMatCheckbox = (dataAutomationId: string) => { const toggleMatCheckbox = (dataAutomationId: string) => {
(getByDataAutomationId(dataAutomationId).nativeElement as HTMLElement).querySelector('input').click(); (unitTestingUtils.getByDataAutomationId(dataAutomationId).nativeElement as HTMLElement).querySelector('input').click();
}; };
const testErrorScriptFormFieldVisibility = (isVisible: boolean) => { const testErrorScriptFormFieldVisibility = (isVisible: boolean) => {
if (isVisible) { if (isVisible) {
expect((getByDataAutomationId('rule-option-form-field-errorScript').nativeElement as HTMLElement).classList).not.toContain( expect((getErrorScriptFormField().nativeElement as HTMLElement).classList).not.toContain('aca-hide-error-script-dropdown');
'aca-hide-error-script-dropdown'
);
} else { } else {
expect((getByDataAutomationId('rule-option-form-field-errorScript').nativeElement as HTMLElement).classList).toContain( expect((getErrorScriptFormField().nativeElement as HTMLElement).classList).toContain('aca-hide-error-script-dropdown');
'aca-hide-error-script-dropdown'
);
} }
}; };
@@ -66,6 +69,7 @@ describe('RuleOptionsUiComponent', () => {
fixture = TestBed.createComponent(RuleOptionsUiComponent); fixture = TestBed.createComponent(RuleOptionsUiComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
loader = TestbedHarnessEnvironment.loader(fixture); loader = TestbedHarnessEnvironment.loader(fixture);
unitTestingUtils = new UnitTestingUtils(fixture.debugElement, loader);
component.writeValue({ component.writeValue({
isEnabled: true, isEnabled: true,
@@ -78,9 +82,9 @@ describe('RuleOptionsUiComponent', () => {
it('should be able to write to the component', () => { it('should be able to write to the component', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(getByDataAutomationId('rule-option-checkbox-asynchronous').componentInstance.checked).toBeFalsy(); expect(getAsynchronousCheckbox().componentInstance.checked).toBeFalsy();
expect(getByDataAutomationId('rule-option-checkbox-inheritable').componentInstance.checked).toBeFalsy(); expect(getInheritableCheckbox().componentInstance.checked).toBeFalsy();
expect(getByDataAutomationId('rule-option-checkbox-disabled').componentInstance.checked).toBeFalsy(); expect(getDisabledCheckbox().componentInstance.checked).toBeFalsy();
testErrorScriptFormFieldVisibility(false); testErrorScriptFormFieldVisibility(false);
component.writeValue({ component.writeValue({
@@ -91,29 +95,29 @@ describe('RuleOptionsUiComponent', () => {
}); });
fixture.detectChanges(); fixture.detectChanges();
expect(getByDataAutomationId('rule-option-checkbox-asynchronous').componentInstance.checked).toBeTruthy(); expect(getAsynchronousCheckbox().componentInstance.checked).toBeTruthy();
expect(getByDataAutomationId('rule-option-checkbox-inheritable').componentInstance.checked).toBeTruthy(); expect(getInheritableCheckbox().componentInstance.checked).toBeTruthy();
expect(getByDataAutomationId('rule-option-checkbox-disabled').componentInstance.checked).toBeTruthy(); expect(getDisabledCheckbox().componentInstance.checked).toBeTruthy();
testErrorScriptFormFieldVisibility(true); testErrorScriptFormFieldVisibility(true);
}); });
it('should enable selector when async checkbox is truthy', () => { it('should enable selector when async checkbox is truthy', () => {
fixture.detectChanges(); fixture.detectChanges();
toggleMatCheckbox('rule-option-checkbox-asynchronous'); toggleMatCheckbox(asynchronousCheckboxAutomationId);
fixture.detectChanges(); fixture.detectChanges();
expect(getByDataAutomationId('rule-option-checkbox-asynchronous').componentInstance.checked).toBeTruthy(); expect(getAsynchronousCheckbox().componentInstance.checked).toBeTruthy();
expect(getByDataAutomationId('rule-option-select-errorScript').componentInstance.disabled).toBeFalsy(); expect(getErrorScriptSelect().componentInstance.disabled).toBeFalsy();
}); });
it('should hide disabled checkbox and unselected checkboxes in read-only mode', () => { it('should hide disabled checkbox and unselected checkboxes in read-only mode', () => {
component.readOnly = true; component.readOnly = true;
fixture.detectChanges(); fixture.detectChanges();
expect(getByDataAutomationId('rule-option-checkbox-asynchronous')).toBeFalsy(); expect(getAsynchronousCheckbox()).toBeFalsy();
expect(getByDataAutomationId('rule-option-checkbox-inheritable')).toBeFalsy(); expect(getInheritableCheckbox()).toBeFalsy();
expect(getByDataAutomationId('rule-option-checkbox-enabled')).toBeFalsy(); expect(getEnabledCheckbox()).toBeFalsy();
expect(getByDataAutomationId('rule-option-select-errorScript')).toBeFalsy(); expect(getErrorScriptSelect()).toBeFalsy();
component.writeValue({ component.writeValue({
isEnabled: false, isEnabled: false,
@@ -123,10 +127,10 @@ describe('RuleOptionsUiComponent', () => {
}); });
fixture.detectChanges(); fixture.detectChanges();
expect(getByDataAutomationId('rule-option-checkbox-asynchronous')).toBeTruthy(); expect(getAsynchronousCheckbox()).toBeTruthy();
expect(getByDataAutomationId('rule-option-checkbox-inheritable')).toBeTruthy(); expect(getInheritableCheckbox()).toBeTruthy();
expect(getByDataAutomationId('rule-option-checkbox-enabled')).toBeFalsy(); expect(getEnabledCheckbox()).toBeFalsy();
expect(getByDataAutomationId('rule-option-select-errorScript')).toBeTruthy(); expect(getErrorScriptSelect()).toBeTruthy();
}); });
it('should populate the error script dropdown with scripts', async () => { it('should populate the error script dropdown with scripts', async () => {
@@ -140,7 +144,7 @@ describe('RuleOptionsUiComponent', () => {
component.ngOnChanges({ errorScriptConstraint: {} as SimpleChange }); component.ngOnChanges({ errorScriptConstraint: {} as SimpleChange });
fixture.detectChanges(); fixture.detectChanges();
(getByDataAutomationId('rule-option-select-errorScript').nativeElement as HTMLElement).click(); unitTestingUtils.clickByDataAutomationId(errorScriptSelectAutomationId);
fixture.detectChanges(); fixture.detectChanges();
const selection = await loader.getHarness(MatSelectHarness); const selection = await loader.getHarness(MatSelectHarness);
@@ -161,7 +165,7 @@ describe('RuleOptionsUiComponent', () => {
component.errorScriptConstraint = errorScriptConstraintMock; component.errorScriptConstraint = errorScriptConstraintMock;
fixture.detectChanges(); fixture.detectChanges();
const matFormField = fixture.debugElement.query(By.css('[data-automation-id="rule-option-form-field-errorScript"')); const matFormField = getErrorScriptFormField();
fixture.detectChanges(); fixture.detectChanges();
expect(matFormField).not.toBeNull(); expect(matFormField).not.toBeNull();
expect(matFormField.componentInstance['floatLabel']).toBe('always'); expect(matFormField.componentInstance['floatLabel']).toBe('always');
@@ -177,10 +181,10 @@ describe('RuleOptionsUiComponent', () => {
}); });
fixture.detectChanges(); fixture.detectChanges();
expect(getByDataAutomationId('rule-option-checkbox-asynchronous').componentInstance.checked).toBeTrue(); expect(getAsynchronousCheckbox().componentInstance.checked).toBeTrue();
expect(getByDataAutomationId('rule-option-checkbox-inheritable').componentInstance.checked).toBeTrue(); expect(getInheritableCheckbox().componentInstance.checked).toBeTrue();
expect(getByDataAutomationId('rule-option-checkbox-disabled').componentInstance.checked).toBeTrue(); expect(getDisabledCheckbox().componentInstance.checked).toBeTrue();
expect(getByDataAutomationId('rule-option-select-errorScript').componentInstance.value).toEqual('1234'); expect(getErrorScriptSelect().componentInstance.value).toEqual('1234');
component.writeValue({ component.writeValue({
isEnabled: false, isEnabled: false,
@@ -190,9 +194,9 @@ describe('RuleOptionsUiComponent', () => {
}); });
fixture.detectChanges(); fixture.detectChanges();
expect(getByDataAutomationId('rule-option-checkbox-asynchronous').componentInstance.checked).toBeFalse(); expect(getAsynchronousCheckbox().componentInstance.checked).toBeFalse();
expect(getByDataAutomationId('rule-option-checkbox-inheritable').componentInstance.checked).toBeTrue(); expect(getInheritableCheckbox().componentInstance.checked).toBeTrue();
expect(getByDataAutomationId('rule-option-checkbox-disabled').componentInstance.checked).toBeTrue(); expect(getDisabledCheckbox().componentInstance.checked).toBeTrue();
testErrorScriptFormFieldVisibility(false); testErrorScriptFormFieldVisibility(false);
}); });
}); });

View File

@@ -25,21 +25,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RuleDetailsUiComponent } from './rule-details.ui-component'; import { RuleDetailsUiComponent } from './rule-details.ui-component';
import { Rule } from '../model/rule.model'; import { Rule } from '../model/rule.model';
import { By } from '@angular/platform-browser';
import { RuleTriggersUiComponent } from './triggers/rule-triggers.ui-component'; import { RuleTriggersUiComponent } from './triggers/rule-triggers.ui-component';
import { RuleOptionsUiComponent } from './options/rule-options.ui-component'; import { RuleOptionsUiComponent } from './options/rule-options.ui-component';
import { RuleActionListUiComponent } from './actions/rule-action-list.ui-component'; import { RuleActionListUiComponent } from './actions/rule-action-list.ui-component';
import { AlfrescoApiService, AlfrescoApiServiceMock, CategoryService } from '@alfresco/adf-content-services'; import { AlfrescoApiService, AlfrescoApiServiceMock, CategoryService } from '@alfresco/adf-content-services';
import { NoopTranslateModule } from '@alfresco/adf-core'; import { NoopTranslateModule, UnitTestingUtils } from '@alfresco/adf-core';
import { ActionParameterConstraint } from '../model/action-parameter-constraint.model';
describe('RuleDetailsUiComponent', () => { describe('RuleDetailsUiComponent', () => {
let fixture: ComponentFixture<RuleDetailsUiComponent>; let fixture: ComponentFixture<RuleDetailsUiComponent>;
let component: RuleDetailsUiComponent; let component: RuleDetailsUiComponent;
let unitTestingUtils: UnitTestingUtils;
const testValue: Partial<Rule> = { const testValue: Partial<Rule> = {
id: 'rule-id', id: 'rule-id',
name: 'Rule name', name: 'Rule name',
description: 'This is the description of the rule', description: 'This is the description of the rule',
isShared: false,
triggers: ['update', 'outbound'], triggers: ['update', 'outbound'],
isAsynchronous: true, isAsynchronous: true,
isInheritable: true, isInheritable: true,
@@ -47,11 +49,8 @@ describe('RuleDetailsUiComponent', () => {
errorScript: '' errorScript: ''
}; };
const getHtmlElement = <T>(dataAutomationId: string) => const getHtmlElement = <T>(dataAutomationId: string): T => unitTestingUtils.getByDataAutomationId(dataAutomationId)?.nativeElement as T;
fixture.debugElement.query(By.css(`[data-automation-id="${dataAutomationId}"]`))?.nativeElement as T; const getComponentInstance = <T>(dataAutomationId: string): T => unitTestingUtils.getByDataAutomationId(dataAutomationId)?.componentInstance as T;
const getComponentInstance = <T>(dataAutomationId: string) =>
fixture.debugElement.query(By.css(`[data-automation-id="${dataAutomationId}"]`))?.componentInstance as T;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -61,6 +60,7 @@ describe('RuleDetailsUiComponent', () => {
fixture = TestBed.createComponent(RuleDetailsUiComponent); fixture = TestBed.createComponent(RuleDetailsUiComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
unitTestingUtils = new UnitTestingUtils(fixture.debugElement);
}); });
it('should fill the form out with initial values', () => { it('should fill the form out with initial values', () => {
@@ -147,8 +147,7 @@ describe('RuleDetailsUiComponent', () => {
describe('RuleActionListUiComponent', () => { describe('RuleActionListUiComponent', () => {
let categoryService: CategoryService; let categoryService: CategoryService;
const getRuleActionsListComponent = (): RuleActionListUiComponent => const getRuleActionsListComponent = (): RuleActionListUiComponent => unitTestingUtils.getByDirective(RuleActionListUiComponent).componentInstance;
fixture.debugElement.query(By.directive(RuleActionListUiComponent)).componentInstance;
beforeEach(() => { beforeEach(() => {
categoryService = TestBed.inject(CategoryService); categoryService = TestBed.inject(CategoryService);
@@ -193,4 +192,25 @@ describe('RuleDetailsUiComponent', () => {
expect(getRuleActionsListComponent().actionDefinitions).toBe(component.actionDefinitions); expect(getRuleActionsListComponent().actionDefinitions).toBe(component.actionDefinitions);
}); });
}); });
it('should return description form control', () => {
component.value = testValue;
fixture.detectChanges();
const descriptionControl = component.description;
expect(descriptionControl.value).toBe(testValue.description);
});
it('should set errorScriptConstraint when parameterConstraints contains script-ref', () => {
const mockConstraints: ActionParameterConstraint[] = [
{ name: 'script-ref', constraints: [] },
{ name: 'other-constraint', constraints: [] }
];
component.parameterConstraints = mockConstraints;
component.ngOnInit();
expect(component.errorScriptConstraint).toBe(mockConstraints[0]);
});
}); });

View File

@@ -84,6 +84,7 @@ export class RuleDetailsUiComponent implements OnInit {
id: newValue.id || FolderRulesService.emptyRule.id, id: newValue.id || FolderRulesService.emptyRule.id,
name: newValue.name || FolderRulesService.emptyRule.name, name: newValue.name || FolderRulesService.emptyRule.name,
description: newValue.description || FolderRulesService.emptyRule.description, description: newValue.description || FolderRulesService.emptyRule.description,
isShared: newValue.isShared || FolderRulesService.emptyRule.isShared,
triggers: newValue.triggers || FolderRulesService.emptyRule.triggers, triggers: newValue.triggers || FolderRulesService.emptyRule.triggers,
conditions: newValue.conditions || FolderRulesService.emptyRule.conditions, conditions: newValue.conditions || FolderRulesService.emptyRule.conditions,
actions: newValue.actions || FolderRulesService.emptyRule.actions, actions: newValue.actions || FolderRulesService.emptyRule.actions,
@@ -146,6 +147,7 @@ export class RuleDetailsUiComponent implements OnInit {
id: new UntypedFormControl(this.value.id), id: new UntypedFormControl(this.value.id),
name: new UntypedFormControl(this.value.name || '', Validators.required), name: new UntypedFormControl(this.value.name || '', Validators.required),
description: new UntypedFormControl(this.value.description || ''), description: new UntypedFormControl(this.value.description || ''),
isShared: new UntypedFormControl(this.value.isShared || false),
triggers: new UntypedFormControl(this.value.triggers || ['inbound'], Validators.required), triggers: new UntypedFormControl(this.value.triggers || ['inbound'], Validators.required),
conditions: new UntypedFormControl( conditions: new UntypedFormControl(
this.value.conditions || { this.value.conditions || {

View File

@@ -24,20 +24,27 @@
import { RuleTriggersUiComponent } from './rule-triggers.ui-component'; import { RuleTriggersUiComponent } from './rule-triggers.ui-component';
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopTranslateModule } from '@alfresco/adf-core'; import { NoopTranslateModule, UnitTestingUtils } from '@alfresco/adf-core';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-content-services'; import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-content-services';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { DebugElement } from '@angular/core';
describe('RuleTriggerUiComponent', () => { describe('RuleTriggerUiComponent', () => {
let fixture: ComponentFixture<RuleTriggersUiComponent>; let fixture: ComponentFixture<RuleTriggersUiComponent>;
let component: RuleTriggersUiComponent; let component: RuleTriggersUiComponent;
let unitTestingUtils: UnitTestingUtils;
let loader: HarnessLoader;
const getByDataAutomationId = (dataAutomationId: string): DebugElement => const checkboxUpdateAutomationId = 'rule-trigger-checkbox-update';
fixture.debugElement.query(By.css(`[data-automation-id="${dataAutomationId}"]`)); const checkboxInboundAutomationId = 'rule-trigger-checkbox-inbound';
const getCheckboxInbound = (): DebugElement => unitTestingUtils.getByDataAutomationId(checkboxInboundAutomationId);
const getCheckboxUpdate = (): DebugElement => unitTestingUtils.getByDataAutomationId(checkboxUpdateAutomationId);
const getCheckboxOutbound = (): DebugElement => unitTestingUtils.getByDataAutomationId('rule-trigger-checkbox-outbound');
const toggleMatCheckbox = (dataAutomationId: string) => { const toggleMatCheckbox = async (dataAutomationId: string) => {
(getByDataAutomationId(dataAutomationId).nativeElement as HTMLElement).querySelector('input').click(); const checkbox = await unitTestingUtils.getMatCheckboxByDataAutomationId(dataAutomationId);
await checkbox.toggle();
}; };
beforeEach(() => { beforeEach(() => {
@@ -48,15 +55,17 @@ describe('RuleTriggerUiComponent', () => {
fixture = TestBed.createComponent(RuleTriggersUiComponent); fixture = TestBed.createComponent(RuleTriggersUiComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
loader = TestbedHarnessEnvironment.loader(fixture);
unitTestingUtils = new UnitTestingUtils(fixture.debugElement, loader);
}); });
it('should default to only the inbound checkbox', () => { it('should default to only the inbound checkbox', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(component.value).toEqual(['inbound']); expect(component.value).toEqual(['inbound']);
expect(getByDataAutomationId('rule-trigger-checkbox-inbound').componentInstance.checked).toBeTruthy(); expect(getCheckboxInbound().componentInstance.checked).toBeTruthy();
expect(getByDataAutomationId('rule-trigger-checkbox-update').componentInstance.checked).toBeFalsy(); expect(getCheckboxUpdate().componentInstance.checked).toBeFalsy();
expect(getByDataAutomationId('rule-trigger-checkbox-outbound').componentInstance.checked).toBeFalsy(); expect(getCheckboxOutbound().componentInstance.checked).toBeFalsy();
}); });
it('should change the checked boxes when the value is written to', () => { it('should change the checked boxes when the value is written to', () => {
@@ -65,25 +74,25 @@ describe('RuleTriggerUiComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(component.value).toEqual(['update', 'outbound']); expect(component.value).toEqual(['update', 'outbound']);
expect(getByDataAutomationId('rule-trigger-checkbox-inbound').componentInstance.checked).toBeFalsy(); expect(getCheckboxInbound().componentInstance.checked).toBeFalsy();
expect(getByDataAutomationId('rule-trigger-checkbox-update').componentInstance.checked).toBeTruthy(); expect(getCheckboxUpdate().componentInstance.checked).toBeTruthy();
expect(getByDataAutomationId('rule-trigger-checkbox-outbound').componentInstance.checked).toBeTruthy(); expect(getCheckboxOutbound().componentInstance.checked).toBeTruthy();
}); });
it('should update the value when a checkbox is checked', () => { it('should update the value when a checkbox is checked', async () => {
const onChangeSpy = spyOn(component, 'onChange'); const onChangeSpy = spyOn(component, 'onChange');
fixture.detectChanges(); fixture.detectChanges();
toggleMatCheckbox('rule-trigger-checkbox-update'); await toggleMatCheckbox(checkboxUpdateAutomationId);
fixture.detectChanges(); fixture.detectChanges();
expect(component.value).toEqual(['inbound', 'update']); expect(component.value).toEqual(['inbound', 'update']);
expect(onChangeSpy).toHaveBeenCalledWith(['inbound', 'update']); expect(onChangeSpy).toHaveBeenCalledWith(['inbound', 'update']);
}); });
it('should update the value when a checkbox is unchecked', () => { it('should update the value when a checkbox is unchecked', async () => {
const onChangeSpy = spyOn(component, 'onChange'); const onChangeSpy = spyOn(component, 'onChange');
fixture.detectChanges(); fixture.detectChanges();
toggleMatCheckbox('rule-trigger-checkbox-inbound'); await toggleMatCheckbox(checkboxInboundAutomationId);
fixture.detectChanges(); fixture.detectChanges();
expect(component.value).toEqual([]); expect(component.value).toEqual([]);
@@ -95,12 +104,12 @@ describe('RuleTriggerUiComponent', () => {
component.writeValue(['update', 'outbound']); component.writeValue(['update', 'outbound']);
fixture.detectChanges(); fixture.detectChanges();
expect(getByDataAutomationId('rule-trigger-checkbox-inbound')).toBeNull(); expect(getCheckboxInbound()).toBeNull();
expect(getByDataAutomationId('rule-trigger-checkbox-update')).toBeNull(); expect(getCheckboxUpdate()).toBeNull();
expect(getByDataAutomationId('rule-trigger-checkbox-outbound')).toBeNull(); expect(getCheckboxOutbound()).toBeNull();
expect(getByDataAutomationId('rule-trigger-value-inbound')).toBeNull(); expect(unitTestingUtils.getByDataAutomationId('rule-trigger-value-inbound')).toBeNull();
expect(getByDataAutomationId('rule-trigger-value-update')).not.toBeNull(); expect(unitTestingUtils.getByDataAutomationId('rule-trigger-value-update')).not.toBeNull();
expect(getByDataAutomationId('rule-trigger-value-outbound')).not.toBeNull(); expect(unitTestingUtils.getByDataAutomationId('rule-trigger-value-outbound')).not.toBeNull();
}); });
}); });

View File

@@ -0,0 +1,80 @@
/*!
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { FormControl, ValidatorFn } from '@angular/forms';
import { ruleCompositeConditionValidator } from './rule-composite-condition.validator';
import { RuleCompositeCondition } from '../../model/rule-composite-condition.model';
describe('ruleCompositeConditionValidator', () => {
let validatorFn: ValidatorFn;
beforeEach(() => {
validatorFn = ruleCompositeConditionValidator();
});
it('should return null for root condition with empty compositeConditions and empty simpleConditions', () => {
const mockCondition = {
compositeConditions: [],
simpleConditions: []
} as RuleCompositeCondition;
const control = new FormControl(mockCondition);
expect(validatorFn(control)).toBeNull();
});
it('should return validation error for nested condition with empty simpleConditions and no nested compositeConditions', () => {
const mockCondition = {
compositeConditions: [
{
compositeConditions: [],
simpleConditions: []
}
],
simpleConditions: []
} as RuleCompositeCondition;
const control = new FormControl(mockCondition);
expect(validatorFn(control)).toEqual({ ruleCompositeConditionInvalid: true });
});
it('should return validation error for deeply nested invalid composite conditions', () => {
const mockCondition = {
compositeConditions: [
{
compositeConditions: [
{
compositeConditions: [],
simpleConditions: []
}
],
simpleConditions: []
}
],
simpleConditions: []
} as RuleCompositeCondition;
const control = new FormControl(mockCondition);
expect(validatorFn(control)).toEqual({ ruleCompositeConditionInvalid: true });
});
});

View File

@@ -25,14 +25,13 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RuleListGroupingUiComponent } from './rule-list-grouping.ui-component'; import { RuleListGroupingUiComponent } from './rule-list-grouping.ui-component';
import { ruleListGroupingItemsMock, rulesMock } from '../../mock/rules.mock'; import { ruleListGroupingItemsMock, rulesMock } from '../../mock/rules.mock';
import { DebugElement } from '@angular/core'; import { NoopTranslateModule, UnitTestingUtils } from '@alfresco/adf-core';
import { By } from '@angular/platform-browser'; import { RuleSet } from '../../model/rule-set.model';
import { NoopTranslateModule } from '@alfresco/adf-core';
describe('RuleListGroupingUiComponent', () => { describe('RuleListGroupingUiComponent', () => {
let component: RuleListGroupingUiComponent; let component: RuleListGroupingUiComponent;
let fixture: ComponentFixture<RuleListGroupingUiComponent>; let fixture: ComponentFixture<RuleListGroupingUiComponent>;
let debugElement: DebugElement; let unitTestingUtils: UnitTestingUtils;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -41,7 +40,7 @@ describe('RuleListGroupingUiComponent', () => {
fixture = TestBed.createComponent(RuleListGroupingUiComponent); fixture = TestBed.createComponent(RuleListGroupingUiComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
debugElement = fixture.debugElement; unitTestingUtils = new UnitTestingUtils(fixture.debugElement);
}); });
it('should display the list of rules', () => { it('should display the list of rules', () => {
@@ -51,16 +50,78 @@ describe('RuleListGroupingUiComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
const rules = debugElement.queryAll(By.css('.aca-rule-list-item')); const rules = unitTestingUtils.getAllByCSS('.aca-rule-list-item');
expect(rules).toBeTruthy('Could not find rules'); expect(rules).toBeTruthy('Could not find rules');
expect(rules.length).toBe(2, 'Unexpected number of rules'); expect(rules.length).toBe(2, 'Unexpected number of rules');
const rule = debugElement.query(By.css('.aca-rule-list-item:first-child')); const name = unitTestingUtils.getByCSS('.aca-rule-list-item:first-child .aca-rule-list-item__header__name');
const name = rule.query(By.css('.aca-rule-list-item__header__name')); const description = unitTestingUtils.getByCSS('.aca-rule-list-item:first-child .aca-rule-list-item__description');
const description = rule.query(By.css('.aca-rule-list-item__description'));
expect(name.nativeElement.textContent).toBe(rulesMock[0].name); expect(name.nativeElement.textContent).toBe(rulesMock[0].name);
expect(description.nativeElement.textContent).toBe(rulesMock[0].description); expect(description.nativeElement.textContent).toBe(rulesMock[0].description);
}); });
it('should emit selectRule event with rule when onRuleClicked is called', () => {
spyOn(component.selectRule, 'emit');
const mockRule = rulesMock[0];
component.onRuleClicked(mockRule);
expect(component.selectRule.emit).toHaveBeenCalledWith(mockRule);
});
it('should return true when rule is selected', () => {
const mockRule = rulesMock[0];
component.selectedRule = mockRule;
const result = component.isSelected(mockRule);
expect(result).toBe(true);
});
it('should return false when rule is not selected', () => {
const [mockRule, differentRule] = rulesMock;
component.selectedRule = mockRule;
const result = component.isSelected(differentRule);
expect(result).toBe(false);
});
it('should return false when no rule is selected', () => {
const mockRule = rulesMock[0];
component.selectedRule = null;
const result = component.isSelected(mockRule);
expect(result).toBe(false);
});
it('should emit ruleEnabledChanged event with tuple when onEnabledChanged is called', () => {
spyOn(component.ruleEnabledChanged, 'emit');
const mockRule = rulesMock[0];
const isEnabled = true;
component.onEnabledChanged(mockRule, isEnabled);
expect(component.ruleEnabledChanged.emit).toHaveBeenCalledWith([mockRule, isEnabled]);
});
it('should emit loadMoreRules event with ruleSet when onClickLoadMoreRules is called', () => {
spyOn(component.loadMoreRules, 'emit');
const mockRuleSet = { id: 'test-rule-set' } as RuleSet;
component.onClickLoadMoreRules(mockRuleSet);
expect(component.loadMoreRules.emit).toHaveBeenCalledWith(mockRuleSet);
});
it('should emit loadMoreRuleSets event when onClickLoadMoreRuleSets is called', () => {
spyOn(component.loadMoreRuleSets, 'emit');
component.onClickLoadMoreRuleSets();
expect(component.loadMoreRuleSets.emit).toHaveBeenCalled();
});
}); });

View File

@@ -0,0 +1,116 @@
/*!
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RuleListItemUiComponent } from './rule-list-item.ui-component';
import { NoopTranslateModule, UnitTestingUtils } from '@alfresco/adf-core';
import { Rule } from '../../model/rule.model';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { HarnessLoader } from '@angular/cdk/testing';
import { MatSlideToggleHarness } from '@angular/material/slide-toggle/testing';
describe('RuleListItemUiComponent', () => {
let component: RuleListItemUiComponent;
let fixture: ComponentFixture<RuleListItemUiComponent>;
let loader: HarnessLoader;
let unitTestingUtils: UnitTestingUtils;
const mockRule = {
id: 'test-rule-id',
name: 'Test Rule',
description: 'Test rule description',
isEnabled: true
} as Rule;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [NoopTranslateModule, RuleListItemUiComponent]
});
fixture = TestBed.createComponent(RuleListItemUiComponent);
component = fixture.componentInstance;
loader = TestbedHarnessEnvironment.loader(fixture);
unitTestingUtils = new UnitTestingUtils(fixture.debugElement);
component.rule = mockRule;
});
it('should display rule name and description', () => {
fixture.detectChanges();
const nameElement = unitTestingUtils.getByCSS('.aca-rule-list-item__header__name');
const descriptionElement = unitTestingUtils.getByCSS('.aca-rule-list-item__description');
expect(nameElement.nativeElement.textContent.trim()).toBe(mockRule.name);
expect(descriptionElement.nativeElement.textContent.trim()).toBe(mockRule.description);
});
it('should show slide toggle when showEnabledToggle is true', async () => {
component.showEnabledToggle = true;
fixture.detectChanges();
const toggleHarness = await loader.getHarnessOrNull(MatSlideToggleHarness);
expect(toggleHarness).toBeTruthy();
});
it('should hide slide toggle when showEnabledToggle is false', async () => {
component.showEnabledToggle = false;
fixture.detectChanges();
const toggleHarness = await loader.getHarnessOrNull(MatSlideToggleHarness);
expect(toggleHarness).toBeFalsy();
});
it('should set toggle checked state based on rule.isEnabled', async () => {
component.showEnabledToggle = true;
component.rule.isEnabled = true;
fixture.detectChanges();
const toggleHarness = await loader.getHarness(MatSlideToggleHarness);
expect(await toggleHarness.isChecked()).toBe(true);
});
describe('onToggleClick', () => {
it('should stop event propagation and emit enabledChanged with isEnabled value', () => {
spyOn(component.enabledChanged, 'emit');
const mockEvent = jasmine.createSpyObj<Event>('Event', ['stopPropagation']);
const isEnabled = true;
component.onToggleClick(isEnabled, mockEvent);
expect(mockEvent.stopPropagation).toHaveBeenCalled();
expect(component.enabledChanged.emit).toHaveBeenCalledWith(isEnabled);
});
it('should stop event propagation and emit enabledChanged with false value', () => {
spyOn(component.enabledChanged, 'emit');
const mockEvent = jasmine.createSpyObj<Event>('Event', ['stopPropagation']);
const isEnabled = false;
component.onToggleClick(isEnabled, mockEvent);
expect(mockEvent.stopPropagation).toHaveBeenCalled();
expect(component.enabledChanged.emit).toHaveBeenCalledWith(isEnabled);
});
});
});

View File

@@ -24,18 +24,20 @@
import { RuleListUiComponent } from './rule-list.ui-component'; import { RuleListUiComponent } from './rule-list.ui-component';
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopTranslateModule } from '@alfresco/adf-core'; import { NoopTranslateModule, UnitTestingUtils } from '@alfresco/adf-core';
import { ownedRuleSetMock, ruleSetsMock, ruleSetWithLinkMock } from '../../mock/rule-sets.mock'; import { ownedRuleSetMock, ruleSetsMock, ruleSetWithLinkMock } from '../../mock/rule-sets.mock';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { owningFolderIdMock } from '../../mock/node.mock'; import { owningFolderIdMock } from '../../mock/node.mock';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-content-services'; import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-content-services';
import { Rule } from '../../model/rule.model';
import { RuleSet } from '../../model/rule-set.model';
describe('RuleListUiComponent', () => { describe('RuleListUiComponent', () => {
let fixture: ComponentFixture<RuleListUiComponent>; let fixture: ComponentFixture<RuleListUiComponent>;
let component: RuleListUiComponent; let component: RuleListUiComponent;
let debugElement: DebugElement; let unitTestingUtils: UnitTestingUtils;
const getMainRuleSetTitleText = (): string => unitTestingUtils.getInnerTextByDataAutomationId('main-rule-set-title');
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -45,7 +47,7 @@ describe('RuleListUiComponent', () => {
fixture = TestBed.createComponent(RuleListUiComponent); fixture = TestBed.createComponent(RuleListUiComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
debugElement = fixture.debugElement; unitTestingUtils = new UnitTestingUtils(fixture.debugElement);
component.folderId = owningFolderIdMock; component.folderId = owningFolderIdMock;
component.inheritedRuleSets = ruleSetsMock; component.inheritedRuleSets = ruleSetsMock;
@@ -55,15 +57,117 @@ describe('RuleListUiComponent', () => {
component.mainRuleSet$ = of(ownedRuleSetMock); component.mainRuleSet$ = of(ownedRuleSetMock);
fixture.detectChanges(); fixture.detectChanges();
const mainRuleSetTitleElement = debugElement.query(By.css(`[data-automation-id="main-rule-set-title"]`)); expect(getMainRuleSetTitleText()).toBe('ACA_FOLDER_RULES.RULE_LIST.OWNED_RULES');
expect((mainRuleSetTitleElement.nativeElement as HTMLDivElement).innerText.trim()).toBe('ACA_FOLDER_RULES.RULE_LIST.OWNED_RULES');
}); });
it('should show "Rules from linked folder" as a title if the main rule set is linked', () => { it('should show "Rules from linked folder" as a title if the main rule set is linked', () => {
component.mainRuleSet$ = of(ruleSetWithLinkMock); component.mainRuleSet$ = of(ruleSetWithLinkMock);
fixture.detectChanges(); fixture.detectChanges();
const mainRuleSetTitleElement = debugElement.query(By.css(`[data-automation-id="main-rule-set-title"]`)); expect(getMainRuleSetTitleText()).toBe('ACA_FOLDER_RULES.RULE_LIST.LINKED_RULES');
expect((mainRuleSetTitleElement.nativeElement as HTMLDivElement).innerText.trim()).toBe('ACA_FOLDER_RULES.RULE_LIST.LINKED_RULES'); });
it('should add loading item when both ruleSetsLoading and hasMoreRuleSets are true', () => {
component.inheritedRuleSets = ruleSetsMock;
component.mainRuleSet$ = of(ruleSetWithLinkMock);
component.ruleSetsLoading = true;
component.hasMoreRuleSets = true;
component.ngOnInit();
const loadingItem = component.inheritedRuleSetGroupingItems.find((item) => item.type === 'loading');
expect(loadingItem).toBeDefined();
expect(loadingItem.type).toBe('loading');
const loadMoreItem = component.inheritedRuleSetGroupingItems.find((item) => item.type === 'load-more-rule-sets');
expect(loadMoreItem).toBeUndefined();
});
it('should add loading item when both loadingRules and hasMoreRules are true', () => {
const ruleSetWithBothFlags: RuleSet = {
...ownedRuleSetMock,
loadingRules: true,
hasMoreRules: true
};
const result = component.getRuleSetGroupingItems(ruleSetWithBothFlags, false);
const loadingItem = result.find((item) => item.type === 'loading');
expect(loadingItem).toBeDefined();
expect(loadingItem.type).toBe('loading');
const loadMoreItem = result.find((item) => item.type === 'load-more-rules');
expect(loadMoreItem).toBeUndefined();
});
it('should not add any special items when neither loadingRules nor hasMoreRules are true', () => {
const ruleSetWithoutFlags: RuleSet = {
...ownedRuleSetMock,
loadingRules: false,
hasMoreRules: false
};
const result = component.getRuleSetGroupingItems(ruleSetWithoutFlags, false);
const specialItems = result.filter((item) => item.type === 'loading' || item.type === 'load-more-rules');
expect(specialItems.length).toBe(0);
});
it('should emit loadMoreRuleSets event when onLoadMoreRuleSets is called', () => {
spyOn(component.loadMoreRuleSets, 'emit');
component.onLoadMoreRuleSets();
expect(component.loadMoreRuleSets.emit).toHaveBeenCalledWith();
});
it('should emit loadMoreRules event with ruleSet when onLoadMoreRules is called', () => {
spyOn(component.loadMoreRules, 'emit');
const mockRuleSet = ownedRuleSetMock;
component.onLoadMoreRules(mockRuleSet);
expect(component.loadMoreRules.emit).toHaveBeenCalledWith(mockRuleSet);
});
it('should emit selectRule event with rule when onSelectRule is called', () => {
spyOn(component.selectRule, 'emit');
const mockRule = ownedRuleSetMock.rules[0];
component.onSelectRule(mockRule);
expect(component.selectRule.emit).toHaveBeenCalledWith(mockRule);
});
it('should stop event propagation and emit ruleSetEditLinkClicked with mainRuleSet when onRuleSetEditLinkClicked is called', () => {
spyOn(component.ruleSetEditLinkClicked, 'emit');
const mockEvent = jasmine.createSpyObj<Event>('Event', ['stopPropagation']);
component.mainRuleSet = ownedRuleSetMock;
component.onRuleSetEditLinkClicked(mockEvent);
expect(mockEvent.stopPropagation).toHaveBeenCalled();
expect(component.ruleSetEditLinkClicked.emit).toHaveBeenCalledWith(ownedRuleSetMock);
});
it('should emit ruleEnabledChanged event with tuple when onRuleEnabledChanged is called', () => {
spyOn(component.ruleEnabledChanged, 'emit');
const mockRule = ownedRuleSetMock.rules[0];
const event: [Rule, boolean] = [mockRule, true];
component.onRuleEnabledChanged(event);
expect(component.ruleEnabledChanged.emit).toHaveBeenCalledWith(event);
});
it('should stop event propagation and emit ruleSetUnlinkClicked with mainRuleSet when onRuleSetUnlinkClicked is called', () => {
spyOn(component.ruleSetUnlinkClicked, 'emit');
const mockEvent = jasmine.createSpyObj<Event>('Event', ['stopPropagation']);
component.mainRuleSet = ownedRuleSetMock;
component.onRuleSetUnlinkClicked(mockEvent);
expect(mockEvent.stopPropagation).toHaveBeenCalled();
expect(component.ruleSetUnlinkClicked.emit).toHaveBeenCalledWith(ownedRuleSetMock);
}); });
}); });

View File

@@ -22,26 +22,44 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>. * from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { RuleSetPickerOptions, RuleSetPickerSmartComponent } from './rule-set-picker.smart-component'; import { RuleSetPickerOptions, RuleSetPickerSmartComponent } from './rule-set-picker.smart-component';
import { NoopAuthModule, NoopTranslateModule } from '@alfresco/adf-core'; import { NoopAuthModule, NoopTranslateModule, NotificationService, UnitTestingUtils } from '@alfresco/adf-core';
import { folderToLinkMock, otherFolderMock } from '../mock/node.mock'; import { folderToLinkMock, otherFolderMock } from '../mock/node.mock';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FolderRuleSetsService } from '../services/folder-rule-sets.service';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { ownedRuleSetMock, ruleSetWithLinkMock, ruleSetWithNoRulesToLinkMock, ruleSetWithOwnedRulesToLinkMock } from '../mock/rule-sets.mock'; import { ruleSetWithLinkMock, ruleSetWithNoRulesToLinkMock, ruleSetWithOwnedRulesToLinkMock } from '../mock/rule-sets.mock';
import { ContentApiService } from '@alfresco/aca-shared'; import { ContentApiService } from '@alfresco/aca-shared';
import { By } from '@angular/platform-browser'; import {
import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-content-services'; AlfrescoApiService,
AlfrescoApiServiceMock,
ContentNodeSelectorPanelComponent,
NodeEntryEvent,
SitesService
} from '@alfresco/adf-content-services';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { Component, DebugElement, EventEmitter, Input, Output } from '@angular/core';
import { RuleSet } from '../model/rule-set.model';
@Component({
selector: 'adf-content-node-selector-panel',
template: '<div data-automation-id="mock-content-node-selector"></div>',
standalone: true
})
class MockContentNodeSelectorPanelComponent {
@Input() currentFolderId: string;
@Output() folderLoaded = new EventEmitter<void>();
@Output() navigationChange = new EventEmitter<NodeEntryEvent>();
@Output() siteChange = new EventEmitter<string>();
}
describe('RuleSetPickerSmartComponent', () => { describe('RuleSetPickerSmartComponent', () => {
let fixture: ComponentFixture<RuleSetPickerSmartComponent>; let fixture: ComponentFixture<RuleSetPickerSmartComponent>;
let component: RuleSetPickerSmartComponent; let component: RuleSetPickerSmartComponent;
let folderRuleSetsService: FolderRuleSetsService;
let loadRuleSetsSpy: jasmine.Spy; let loadRuleSetsSpy: jasmine.Spy;
let callApiSpy: jasmine.Spy; let sitesService: SitesService;
let unitTestingUtils: UnitTestingUtils;
const dialogRef = { const dialogRef = {
close: jasmine.createSpy('close'), close: jasmine.createSpy('close'),
@@ -53,6 +71,9 @@ describe('RuleSetPickerSmartComponent', () => {
defaultNodeId: 'folder-1-id' defaultNodeId: 'folder-1-id'
}; };
const getItems = (): DebugElement[] => unitTestingUtils.getAllByCSS('.aca-rule-set-picker__content__rule-list aca-rule-list-item');
const getEmptyList = (): DebugElement => unitTestingUtils.getByCSS('adf-empty-content');
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [NoopTranslateModule, NoopAuthModule, RuleSetPickerSmartComponent], imports: [NoopTranslateModule, NoopAuthModule, RuleSetPickerSmartComponent],
@@ -73,27 +94,37 @@ describe('RuleSetPickerSmartComponent', () => {
} }
} }
] ]
}).overrideComponent(RuleSetPickerSmartComponent, {
remove: {
imports: [ContentNodeSelectorPanelComponent]
},
add: {
imports: [MockContentNodeSelectorPanelComponent]
}
}); });
folderRuleSetsService = TestBed.inject(FolderRuleSetsService);
fixture = TestBed.createComponent(RuleSetPickerSmartComponent); fixture = TestBed.createComponent(RuleSetPickerSmartComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
sitesService = TestBed.inject(SitesService);
unitTestingUtils = new UnitTestingUtils(fixture.debugElement);
loadRuleSetsSpy = spyOn(component.folderRuleSetsService, 'loadRuleSets').and.callThrough(); loadRuleSetsSpy = spyOn(component.folderRuleSetsService, 'loadRuleSets');
callApiSpy = spyOn<any>(folderRuleSetsService, 'callApi'); spyOn(sitesService, 'getSites');
callApiSpy
.withArgs(`/nodes/${dialogOptions.nodeId}/rule-sets?include=isLinkedTo,owningFolder,linkedToBy&skipCount=0&maxItems=100`, 'GET')
.and.returnValue(Promise.resolve(ownedRuleSetMock))
.withArgs(`/nodes/${dialogOptions.nodeId}/rule-sets/-default-?include=isLinkedTo,owningFolder,linkedToBy`, 'GET')
.and.returnValue(Promise.resolve(ownedRuleSetMock))
.withArgs(`/nodes/${folderToLinkMock.id}?include=path%2Cproperties%2CallowableOperations%2Cpermissions`, 'GET')
.and.returnValue(Promise.resolve({ entry: folderToLinkMock }));
}); });
afterEach(() => { afterEach(() => {
fixture.destroy(); fixture.destroy();
}); });
it('should set true to rulesLoading$ when rulesLoading or folderLoading is true', (done) => {
component.setFolderLoading(true);
component.rulesLoading$.subscribe((result) => {
expect(result).toBe(true);
done();
});
});
it('should load the rule sets of a node once it has been selected', () => { it('should load the rule sets of a node once it has been selected', () => {
expect(loadRuleSetsSpy).not.toHaveBeenCalled(); expect(loadRuleSetsSpy).not.toHaveBeenCalled();
component.onNodeSelect([folderToLinkMock]); component.onNodeSelect([folderToLinkMock]);
@@ -108,11 +139,8 @@ describe('RuleSetPickerSmartComponent', () => {
component.onNodeSelect([folderToLinkMock]); component.onNodeSelect([folderToLinkMock]);
fixture.detectChanges(); fixture.detectChanges();
const items = fixture.debugElement.queryAll(By.css('.aca-rule-set-picker__content__rule-list aca-rule-list-item')); expect(getItems().length).toBe(0);
expect(items.length).toBe(0); expect(getEmptyList()).not.toBeNull();
const emptyList = fixture.debugElement.query(By.css('adf-empty-content'));
expect(emptyList).not.toBeNull();
}); });
it('should show an empty list message if a selected folder has linked rules', () => { it('should show an empty list message if a selected folder has linked rules', () => {
@@ -121,11 +149,8 @@ describe('RuleSetPickerSmartComponent', () => {
component.onNodeSelect([folderToLinkMock]); component.onNodeSelect([folderToLinkMock]);
fixture.detectChanges(); fixture.detectChanges();
const items = fixture.debugElement.queryAll(By.css('.aca-rule-set-picker__content__rule-list aca-rule-list-item')); expect(getItems().length).toBe(0);
expect(items.length).toBe(0); expect(getEmptyList()).not.toBeNull();
const emptyList = fixture.debugElement.query(By.css('adf-empty-content'));
expect(emptyList).not.toBeNull();
}); });
it('should show a list of items if a selected folder has owned rules', () => { it('should show a list of items if a selected folder has owned rules', () => {
@@ -134,10 +159,62 @@ describe('RuleSetPickerSmartComponent', () => {
component.onNodeSelect([folderToLinkMock]); component.onNodeSelect([folderToLinkMock]);
fixture.detectChanges(); fixture.detectChanges();
const items = fixture.debugElement.queryAll(By.css('.aca-rule-set-picker__content__rule-list aca-rule-list-item')); expect(getItems().length).toBe(2);
expect(items.length).toBe(2); expect(getEmptyList()).toBeNull();
});
const emptyList = fixture.debugElement.query(By.css('adf-empty-content')); describe('onSubmit', () => {
expect(emptyList).toBeNull(); let deleteRuleSetLinkSpy: jasmine.Spy<(nodeId: string, ruleSetId: string) => Promise<void>>;
let createRuleSetLinkSpy: jasmine.Spy<(nodeId: string, linkedNodeId: string) => Promise<void>>;
beforeEach(() => {
deleteRuleSetLinkSpy = spyOn(component.folderRuleSetsService, 'deleteRuleSetLink').and.returnValue(Promise.resolve());
createRuleSetLinkSpy = spyOn(component.folderRuleSetsService, 'createRuleSetLink').and.returnValue(Promise.resolve());
component['selectedNodeId'] = 'selected-node-id';
});
it('should set isBusy to true and create rule set link when no existing rule set', fakeAsync(() => {
component.existingRuleSet = null;
component.onSubmit();
expect(component.isBusy).toBe(true);
expect(deleteRuleSetLinkSpy).not.toHaveBeenCalled();
tick();
expect(createRuleSetLinkSpy).toHaveBeenCalledWith(component.nodeId, 'selected-node-id');
expect(dialogRef.close).toHaveBeenCalledWith(true);
expect(component.isBusy).toBe(false);
}));
it('should delete existing rule set link then create new one when existing rule set provided', fakeAsync(() => {
component.existingRuleSet = { id: 'existing-rule-set-id' } as RuleSet;
component.onSubmit();
expect(component.isBusy).toBe(true);
expect(deleteRuleSetLinkSpy).toHaveBeenCalledWith(component.nodeId, 'existing-rule-set-id');
tick();
expect(createRuleSetLinkSpy).toHaveBeenCalledWith(component.nodeId, 'selected-node-id');
expect(dialogRef.close).toHaveBeenCalledWith(true);
expect(component.isBusy).toBe(false);
}));
it('should handle error and call handleError when deleteRuleSetLink fails', fakeAsync(() => {
const notificationService = TestBed.inject(NotificationService);
spyOn(notificationService, 'showError');
deleteRuleSetLinkSpy.and.returnValue(Promise.reject(new Error('delete error')));
component.existingRuleSet = { id: 'existing-rule-set-id' } as RuleSet;
component.onSubmit();
tick();
expect(component.isBusy).toBe(false);
expect(notificationService.showError).toHaveBeenCalledWith('ACA_FOLDER_RULES.LINK_RULES_DIALOG.ERRORS.REQUEST_FAILED');
}));
}); });
}); });

View File

@@ -27,38 +27,76 @@ import { TestBed } from '@angular/core/testing';
import { FolderRulesService } from './folder-rules.service'; import { FolderRulesService } from './folder-rules.service';
import { ContentApiService } from '@alfresco/aca-shared'; import { ContentApiService } from '@alfresco/aca-shared';
import { getOtherFolderEntryMock, getOwningFolderEntryMock, otherFolderIdMock, owningFolderIdMock, owningFolderMock } from '../mock/node.mock'; import { getOtherFolderEntryMock, getOwningFolderEntryMock, otherFolderIdMock, owningFolderIdMock, owningFolderMock } from '../mock/node.mock';
import { of } from 'rxjs'; import { filter, firstValueFrom, lastValueFrom, of, skip, throwError } from 'rxjs';
import { getDefaultRuleSetResponseMock, getRuleSetsResponseMock, inheritedRuleSetMock, ownedRuleSetMock } from '../mock/rule-sets.mock'; import { getDefaultRuleSetResponseMock, getRuleSetsResponseMock, inheritedRuleSetMock, ownedRuleSetMock } from '../mock/rule-sets.mock';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { inheritedRulesMock, linkedRulesMock, ownedRulesMock, ruleMock } from '../mock/rules.mock'; import { inheritedRulesMock, linkedRulesMock, ownedRulesMock, ruleMock } from '../mock/rules.mock';
import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-content-services'; import { AlfrescoApiService } from '@alfresco/adf-content-services';
import { NoopTranslateModule } from '@alfresco/adf-core'; import { NoopTranslateModule } from '@alfresco/adf-core';
import { HttpErrorResponse } from '@angular/common/http';
import { Rule } from '../model/rule.model';
describe('FolderRuleSetsService', () => { describe('FolderRuleSetsService', () => {
let folderRuleSetsService: FolderRuleSetsService; let folderRuleSetsService: FolderRuleSetsService;
let folderRulesService: FolderRulesService; let folderRulesService: FolderRulesService;
let contentApiService: ContentApiService; let contentApiService: ContentApiService;
let callApiSpy: jasmine.Spy; let apiClientSpy: jasmine.SpyObj<{ callApi: jasmine.Spy }>;
let getRulesSpy: jasmine.Spy; let getRulesSpy: jasmine.Spy;
let getNodeSpy: jasmine.Spy; let getNodeSpy: jasmine.Spy;
beforeEach(() => { beforeEach(() => {
apiClientSpy = jasmine.createSpyObj('contentPrivateClient', ['callApi']);
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [NoopTranslateModule], imports: [NoopTranslateModule],
providers: [FolderRuleSetsService, FolderRulesService, ContentApiService, { provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }] providers: [
ContentApiService,
{
provide: AlfrescoApiService,
useValue: {
getInstance: () => ({
contentPrivateClient: apiClientSpy
})
}
}
]
}); });
folderRuleSetsService = TestBed.inject(FolderRuleSetsService); folderRuleSetsService = TestBed.inject(FolderRuleSetsService);
folderRulesService = TestBed.inject(FolderRulesService); folderRulesService = TestBed.inject(FolderRulesService);
contentApiService = TestBed.inject(ContentApiService); contentApiService = TestBed.inject(ContentApiService);
callApiSpy = spyOn<any>(folderRuleSetsService, 'callApi') apiClientSpy.callApi
.withArgs(`/nodes/${owningFolderIdMock}/rule-sets/-default-?include=isLinkedTo,owningFolder,linkedToBy`, 'GET') .withArgs(
`/nodes/${owningFolderIdMock}/rule-sets/-default-?include=isLinkedTo,owningFolder,linkedToBy`,
'GET',
{},
{},
{},
{},
{},
['application/json'],
['application/json']
)
.and.returnValue(of(getDefaultRuleSetResponseMock)) .and.returnValue(of(getDefaultRuleSetResponseMock))
.withArgs(`/nodes/${owningFolderIdMock}/rule-sets?include=isLinkedTo,owningFolder,linkedToBy&skipCount=0&maxItems=100`, 'GET') .withArgs(
`/nodes/${owningFolderIdMock}/rule-sets?include=isLinkedTo,owningFolder,linkedToBy&skipCount=0&maxItems=100`,
'GET',
{},
{},
{},
{},
{},
['application/json'],
['application/json']
)
.and.returnValue(of(getRuleSetsResponseMock)) .and.returnValue(of(getRuleSetsResponseMock))
.and.stub(); .withArgs('/nodes/folder-1-id/rule-set-links', 'POST', {}, {}, {}, {}, { id: 'folder-2-id' }, ['application/json'], ['application/json'])
.and.returnValue(of({}))
.withArgs('/nodes/folder-1-id/rule-set-links/rule-set-1-id', 'DELETE', {}, {}, {}, {}, {}, ['application/json'], ['application/json'])
.and.returnValue(of({}));
getRulesSpy = spyOn<any>(folderRulesService, 'getRules') getRulesSpy = spyOn<any>(folderRulesService, 'getRules')
.withArgs(jasmine.anything(), 'rule-set-no-links') .withArgs(jasmine.anything(), 'rule-set-no-links')
.and.returnValue(of({ rules: ownedRulesMock, hasMoreRules: false })) .and.returnValue(of({ rules: ownedRulesMock, hasMoreRules: false }))
@@ -72,17 +110,37 @@ describe('FolderRuleSetsService', () => {
.and.returnValue(of(getOwningFolderEntryMock)) .and.returnValue(of(getOwningFolderEntryMock))
.withArgs(otherFolderIdMock) .withArgs(otherFolderIdMock)
.and.returnValue(of(getOtherFolderEntryMock)); .and.returnValue(of(getOtherFolderEntryMock));
spyOn(folderRulesService, 'selectRule');
}); });
it('should have an initial value of null for selectedRuleSet$', async () => { it('should have an initial value of null for selectedRuleSet$', async () => {
const selectedRuleSetPromise = folderRuleSetsService.selectedRuleSet$.pipe(take(1)).toPromise(); const selectedRuleSetPromise = firstValueFrom(folderRuleSetsService.selectedRuleSet$.pipe(take(1)));
const selectedRuleSet = await selectedRuleSetPromise; const selectedRuleSet = await selectedRuleSetPromise;
expect(selectedRuleSet).toBeNull(); expect(selectedRuleSet).toBeNull();
}); });
it('should select the first rule of the owned rule set of the folder', async () => {
// take(3), because: 1 = init of the BehaviourSubject, 2 = reinitialise at beginning of loadRuleSets, 3 = in subscribe
const mainRuleSetPromise = firstValueFrom(folderRuleSetsService.mainRuleSet$.pipe(skip(3), take(1)));
folderRuleSetsService.loadRuleSets(owningFolderIdMock);
folderRuleSetsService.removeRuleFromMainRuleSet('owned-rule-1-id');
const mainRuleSet = await mainRuleSetPromise;
expect(mainRuleSet.rules[0].id).toBe('owned-rule-2-id');
expect(folderRulesService.selectRule).toHaveBeenCalledWith(ruleMock('owned-rule-1'));
});
it('should not call selectRule on removeRuleFromMainRuleSet if mainRuleSet not set', async () => {
folderRuleSetsService.removeRuleFromMainRuleSet('owned-rule-1-id');
expect(folderRulesService.selectRule).not.toHaveBeenCalled();
});
it(`should load node info when loading the node's rule sets`, async () => { it(`should load node info when loading the node's rule sets`, async () => {
// take(2), because: 1 = init of the BehaviourSubject, 2 = in subscribe // take(2), because: 1 = init of the BehaviourSubject, 2 = in subscribe
const folderInfoPromise = folderRuleSetsService.folderInfo$.pipe(take(2)).toPromise(); const folderInfoPromise = lastValueFrom(folderRuleSetsService.folderInfo$.pipe(take(2)));
folderRuleSetsService.loadRuleSets(owningFolderIdMock); folderRuleSetsService.loadRuleSets(owningFolderIdMock);
const folderInfo = await folderInfoPromise; const folderInfo = await folderInfoPromise;
@@ -93,68 +151,303 @@ describe('FolderRuleSetsService', () => {
it('should load the main rule set (main or linked)', async () => { it('should load the main rule set (main or linked)', async () => {
// take(3), because: 1 = init of the BehaviourSubject, 2 = reinitialise at beginning of loadRuleSets, 3 = in subscribe // take(3), because: 1 = init of the BehaviourSubject, 2 = reinitialise at beginning of loadRuleSets, 3 = in subscribe
const mainRuleSetPromise = folderRuleSetsService.mainRuleSet$.pipe(take(3)).toPromise(); const mainRuleSetPromise = lastValueFrom(folderRuleSetsService.mainRuleSet$.pipe(take(3)));
folderRuleSetsService.loadRuleSets(owningFolderIdMock); folderRuleSetsService.loadRuleSets(owningFolderIdMock);
const ruleSet = await mainRuleSetPromise; const ruleSet = await mainRuleSetPromise;
expect(callApiSpy).toHaveBeenCalledWith(`/nodes/${owningFolderIdMock}/rule-sets/-default-?include=isLinkedTo,owningFolder,linkedToBy`, 'GET'); expect(apiClientSpy.callApi).toHaveBeenCalledWith(
`/nodes/${owningFolderIdMock}/rule-sets/-default-?include=isLinkedTo,owningFolder,linkedToBy`,
'GET',
{},
{},
{},
{},
{},
['application/json'],
['application/json']
);
expect(ruleSet).toEqual(ownedRuleSetMock); expect(ruleSet).toEqual(ownedRuleSetMock);
}); });
it('should load inherited rule sets of a node and filter out owned or inherited rule sets', async () => { it('should load inherited rule sets of a node and filter out owned or inherited rule sets', async () => {
// take(3), because: 1 = init of the BehaviourSubject, 2 = reinitialise at beginning of loadRuleSets, 3 = in subscribe // take(3), because: 1 = init of the BehaviourSubject, 2 = reinitialise at beginning of loadRuleSets, 3 = in subscribe
const inheritedRuleSetsPromise = folderRuleSetsService.inheritedRuleSets$.pipe(take(3)).toPromise(); const inheritedRuleSetsPromise = lastValueFrom(folderRuleSetsService.inheritedRuleSets$.pipe(take(3)));
const hasMoreRuleSetsPromise = folderRuleSetsService.hasMoreRuleSets$.pipe(take(3)).toPromise(); const hasMoreRuleSetsPromise = lastValueFrom(folderRuleSetsService.hasMoreRuleSets$.pipe(take(3)));
folderRuleSetsService.loadRuleSets(owningFolderIdMock); folderRuleSetsService.loadRuleSets(owningFolderIdMock);
const ruleSets = await inheritedRuleSetsPromise; const ruleSets = await inheritedRuleSetsPromise;
const hasMoreRuleSets = await hasMoreRuleSetsPromise; const hasMoreRuleSets = await hasMoreRuleSetsPromise;
expect(callApiSpy).toHaveBeenCalledWith( expect(apiClientSpy.callApi).toHaveBeenCalledWith(
`/nodes/${owningFolderIdMock}/rule-sets?include=isLinkedTo,owningFolder,linkedToBy&skipCount=0&maxItems=100`, `/nodes/${owningFolderIdMock}/rule-sets?include=isLinkedTo,owningFolder,linkedToBy&skipCount=0&maxItems=100`,
'GET' 'GET',
{},
{},
{},
{},
{},
['application/json'],
['application/json']
); );
expect(ruleSets).toEqual([inheritedRuleSetMock]); expect(ruleSets).toEqual([inheritedRuleSetMock]);
expect(getRulesSpy).toHaveBeenCalledWith(owningFolderIdMock, jasmine.anything()); expect(getRulesSpy).toHaveBeenCalledWith(owningFolderIdMock, jasmine.anything());
expect(hasMoreRuleSets).toEqual(false); expect(hasMoreRuleSets).toEqual(false);
}); });
it('should select the first rule of the owned rule set of the folder', async () => { it('should append additional inherited rule sets on loadMoreInheritedRuleSets', (done) => {
const selectRuleSpy = spyOn(folderRulesService, 'selectRule');
// take(3), because: 1 = init of the BehaviourSubject, 2 = reinitialise at beginning of loadRuleSets, 3 = in subscribe
const ruleSetListingPromise = folderRuleSetsService.inheritedRuleSets$.pipe(take(3)).toPromise();
folderRuleSetsService.loadRuleSets(owningFolderIdMock); folderRuleSetsService.loadRuleSets(owningFolderIdMock);
await ruleSetListingPromise;
expect(selectRuleSpy).toHaveBeenCalledWith(ruleMock('owned-rule-1')); folderRuleSetsService.inheritedRuleSets$.pipe(take(3)).subscribe(async () => {
const additionalRuleSet = {
...inheritedRuleSetMock,
id: 'additional-inherited-rule-set',
owningFolder: otherFolderIdMock
};
apiClientSpy.callApi
.withArgs(
`/nodes/${owningFolderIdMock}/rule-sets?include=isLinkedTo,owningFolder,linkedToBy&skipCount=1&maxItems=100`,
'GET',
{},
{},
{},
{},
{},
['application/json'],
['application/json']
)
.and.returnValue(
of({
list: {
entries: [{ entry: additionalRuleSet }],
pagination: { hasMoreItems: false }
}
})
);
getRulesSpy
.withArgs(jasmine.anything(), 'additional-inherited-rule-set')
.and.returnValue(of({ rules: inheritedRulesMock, hasMoreRules: false }));
let updateCount = 0;
const subscription = folderRuleSetsService.inheritedRuleSets$.subscribe((ruleSets) => {
updateCount++;
if (updateCount === 2) {
subscription.unsubscribe();
expect(ruleSets[0]).toEqual(inheritedRuleSetMock);
expect(ruleSets[1].id).toBe('additional-inherited-rule-set');
expect(apiClientSpy.callApi).toHaveBeenCalledWith(
`/nodes/${owningFolderIdMock}/rule-sets?include=isLinkedTo,owningFolder,linkedToBy&skipCount=1&maxItems=100`,
'GET',
{},
{},
{},
{},
{},
['application/json'],
['application/json']
);
done();
}
});
folderRuleSetsService.loadMoreInheritedRuleSets();
});
}); });
it('should select a different rule when removing a rule', () => { describe('Error handling', () => {
const selectRuleSpy = spyOn(folderRulesService, 'selectRule'); function testRuleSetApiError(status: number, statusText: string, errorMessage: string, done: DoneFn) {
folderRuleSetsService['mainRuleSet'] = JSON.parse(JSON.stringify(ownedRuleSetMock)); const httpError = new HttpErrorResponse({
folderRuleSetsService['inheritedRuleSets'] = JSON.parse(JSON.stringify([inheritedRuleSetMock])); status,
statusText,
error: { message: errorMessage }
});
apiClientSpy.callApi
.withArgs(
`/nodes/${owningFolderIdMock}/rule-sets/-default-?include=isLinkedTo,owningFolder,linkedToBy`,
'GET',
{},
{},
{},
{},
{},
['application/json'],
['application/json']
)
.and.returnValue(throwError(() => httpError));
folderRuleSetsService.mainRuleSet$.pipe(skip(1), take(1)).subscribe((value) => {
expect(value).toBeNull();
done();
});
folderRuleSetsService.loadRuleSets(owningFolderIdMock, false);
}
function testNodeInfoError(service: FolderRuleSetsService, status: number, expectedValue: null | undefined, done: DoneFn) {
const httpError = new HttpErrorResponse({ status, statusText: status === 404 ? 'Not Found' : 'Failed' });
getNodeSpy.withArgs(owningFolderIdMock).and.returnValue(throwError(() => httpError));
service.folderInfo$.pipe(skip(1), take(1)).subscribe((info) => {
expect(info).toEqual(expectedValue);
done();
});
service.loadRuleSets(owningFolderIdMock, false);
}
it('should set main rule set to null on 404 error', (done) => {
testRuleSetApiError(404, 'Not Found', 'Rule set not found', done);
});
it('should set mainRuleSet$ to null on non-404 error', (done) => {
testRuleSetApiError(400, 'Failed', 'Failed to fetch main rule set', done);
});
it('should emit null folderInfo when getNodeInfo fails with 404', (done) => {
testNodeInfoError(folderRuleSetsService, 404, null, done);
});
it('should emit undefined folderInfo on getNodeInfo non-404 error', (done) => {
testNodeInfoError(folderRuleSetsService, 400, undefined, done);
});
});
it('should emit null folderInfo when nodeId is empty', (done) => {
apiClientSpy.callApi
.withArgs(
`/nodes//rule-sets/-default-?include=isLinkedTo,owningFolder,linkedToBy`,
'GET',
{},
{},
{},
{},
{},
['application/json'],
['application/json']
)
.and.returnValue(throwError(() => new Error('Node ID is empty')));
folderRuleSetsService.folderInfo$.pipe(skip(1), take(1)).subscribe((info) => {
expect(info).toBeNull();
expect(getNodeSpy).not.toHaveBeenCalled();
done();
});
folderRuleSetsService.loadRuleSets('', false);
});
it('should add new rule to main rule set', async () => {
folderRuleSetsService.loadRuleSets(owningFolderIdMock);
const newRule = ruleMock('new-rule');
await firstValueFrom(
folderRuleSetsService.isLoading$.pipe(
filter((loading) => !loading),
take(1)
)
);
folderRuleSetsService.addOrUpdateRuleInMainRuleSet(newRule);
const main = await firstValueFrom(folderRuleSetsService.mainRuleSet$.pipe(take(1)));
expect(main.rules[2]).toEqual(newRule);
});
it('should update existing rule in main rule set', async () => {
folderRuleSetsService.loadRuleSets(owningFolderIdMock);
const newRule = { ...ownedRulesMock[0], description: 'new description' } as Rule;
folderRuleSetsService.addOrUpdateRuleInMainRuleSet(newRule);
const main = await firstValueFrom(folderRuleSetsService.mainRuleSet$.pipe(take(1)));
expect(main.rules[0].description).toEqual(newRule.description);
});
it('should set main rule set to null if last rules was removed', async () => {
getRulesSpy.withArgs(jasmine.anything(), 'rule-set-no-links').and.returnValue(of({ rules: [ownedRulesMock[0]], hasMoreRules: false }));
folderRuleSetsService.loadRuleSets(owningFolderIdMock);
await firstValueFrom(
folderRuleSetsService.isLoading$.pipe(
filter((loading) => !loading),
take(1)
)
);
folderRuleSetsService.removeRuleFromMainRuleSet('owned-rule-1-id'); folderRuleSetsService.removeRuleFromMainRuleSet('owned-rule-1-id');
expect(selectRuleSpy).toHaveBeenCalledWith(ruleMock('owned-rule-2')); const main = await lastValueFrom(folderRuleSetsService.mainRuleSet$.pipe(take(1)));
selectRuleSpy.calls.reset(); expect(main).toBe(null);
folderRuleSetsService.removeRuleFromMainRuleSet('owned-rule-2-id');
expect(selectRuleSpy).toHaveBeenCalledWith(ruleMock('inherited-rule-1'));
}); });
it('should send a POST request to create a new link between two folders', async () => { it('should send a POST request to create a new link between two folders', async () => {
await folderRuleSetsService.createRuleSetLink('folder-1-id', 'folder-2-id'); await folderRuleSetsService.createRuleSetLink('folder-1-id', 'folder-2-id');
expect(callApiSpy).toHaveBeenCalledWith('/nodes/folder-1-id/rule-set-links', 'POST', { expect(apiClientSpy.callApi).toHaveBeenCalledWith(
id: 'folder-2-id' '/nodes/folder-1-id/rule-set-links',
}); 'POST',
{},
{},
{},
{},
{ id: 'folder-2-id' },
['application/json'],
['application/json']
);
}); });
it('should send a DELETE request to delete a link between two folders', async () => { it('should call refreshMainRuleSet when main rule set is empty', (done) => {
const newRule = ruleMock('owned-rule-33');
apiClientSpy.callApi
.withArgs(
`/nodes/${owningFolderIdMock}/rule-sets/-default-?include=isLinkedTo,owningFolder,linkedToBy`,
'GET',
{},
{},
{},
{},
{},
['application/json'],
['application/json']
)
.and.returnValue(throwError(() => new Error('Main rule set not found')));
folderRuleSetsService.loadRuleSets(owningFolderIdMock);
folderRuleSetsService.mainRuleSet$.pipe(skip(0), take(1)).subscribe((ruleSet) => {
expect(ruleSet).toBeNull();
done();
});
folderRuleSetsService.addOrUpdateRuleInMainRuleSet(newRule);
});
it('should refreshMainRuleSet and select a rule when main rule set exists', () => {
folderRuleSetsService.loadRuleSets(owningFolderIdMock);
const newRule = ruleMock('new-rule');
folderRuleSetsService.refreshMainRuleSet(newRule);
expect(folderRulesService.selectRule).toHaveBeenCalled();
});
it('should send a DELETE request to remove a rule set link', async () => {
await folderRuleSetsService.deleteRuleSetLink('folder-1-id', 'rule-set-1-id'); await folderRuleSetsService.deleteRuleSetLink('folder-1-id', 'rule-set-1-id');
expect(callApiSpy).toHaveBeenCalledWith('/nodes/folder-1-id/rule-set-links/rule-set-1-id', 'DELETE');
expect(apiClientSpy.callApi).toHaveBeenCalledWith(
'/nodes/folder-1-id/rule-set-links/rule-set-1-id',
'DELETE',
{},
{},
{},
{},
{},
['application/json'],
['application/json']
);
}); });
}); });

View File

@@ -194,7 +194,7 @@ export class FolderRuleSetsService {
} }
return combineLatest( return combineLatest(
this.currentFolder?.id === entry.owningFolder ? of(this.currentFolder) : this.getNodeInfo(entry.owningFolder || ''), this.currentFolder?.id === entry.owningFolder ? of(this.currentFolder) : this.getNodeInfo(entry.owningFolder || ''),
this.folderRulesService.getRules(this.currentFolder.id || '', entry.id) this.folderRulesService.getRules(this.currentFolder?.id || '', entry.id)
).pipe( ).pipe(
map(([owningFolderNodeInfo, getRulesRes]) => ({ map(([owningFolderNodeInfo, getRulesRes]) => ({
id: entry.id, id: entry.id,
@@ -209,28 +209,28 @@ export class FolderRuleSetsService {
} }
removeRuleFromMainRuleSet(ruleId: string) { removeRuleFromMainRuleSet(ruleId: string) {
if (this.mainRuleSet) { if (!this.mainRuleSet) {
const index = this.mainRuleSet.rules.findIndex((rule: Rule) => rule.id === ruleId); return;
if (index > -1) {
if (this.mainRuleSet.rules.length > 1) {
this.mainRuleSet.rules.splice(index, 1);
} else {
this.mainRuleSet = null;
}
this.mainRuleSetSource.next(this.mainRuleSet);
this.folderRulesService.selectRule(this.mainRuleSet?.rules[0] ?? this.inheritedRuleSets[0]?.rules[0] ?? null);
}
} }
const updatedRules = this.mainRuleSet.rules.filter((rule) => rule.id !== ruleId);
const newMainRuleSet: RuleSet = updatedRules.length ? { ...this.mainRuleSet, rules: updatedRules } : null;
this.mainRuleSet = newMainRuleSet;
this.mainRuleSetSource.next(newMainRuleSet);
const nextRule = newMainRuleSet?.rules[0] ?? this.inheritedRuleSets[0]?.rules[0] ?? null;
this.folderRulesService.selectRule(nextRule);
} }
addOrUpdateRuleInMainRuleSet(newRule: Rule) { addOrUpdateRuleInMainRuleSet(newRule: Rule) {
if (this.mainRuleSet) { if (this.mainRuleSet) {
const index = this.mainRuleSet.rules.findIndex((rule: Rule) => rule.id === newRule.id); const updatedRules = this.mainRuleSet.rules.some((rule) => rule.id === newRule.id)
if (index > -1) { ? this.mainRuleSet.rules.map((rule) => (rule.id === newRule.id ? newRule : rule))
this.mainRuleSet.rules.splice(index, 1, newRule); : [...this.mainRuleSet.rules, newRule];
} else {
this.mainRuleSet.rules.push(newRule); this.mainRuleSet = { ...this.mainRuleSet, rules: updatedRules };
}
this.mainRuleSetSource.next(this.mainRuleSet); this.mainRuleSetSource.next(this.mainRuleSet);
this.folderRulesService.selectRule(newRule); this.folderRulesService.selectRule(newRule);
} else { } else {

View File

@@ -38,13 +38,13 @@ import {
import { ruleSetMock } from '../mock/rule-sets.mock'; import { ruleSetMock } from '../mock/rule-sets.mock';
import { owningFolderIdMock } from '../mock/node.mock'; import { owningFolderIdMock } from '../mock/node.mock';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-content-services'; import { AlfrescoApiService } from '@alfresco/adf-content-services';
describe('FolderRulesService', () => { describe('FolderRulesService', () => {
let folderRulesService: FolderRulesService; let folderRulesService: FolderRulesService;
let notificationService: NotificationService; let notificationService: NotificationService;
let callApiSpy: jasmine.Spy; let apiClientSpy: jasmine.SpyObj<{ callApi: jasmine.Spy }>;
const nodeId = owningFolderIdMock; const nodeId = owningFolderIdMock;
const ruleSetId = 'rule-set-id'; const ruleSetId = 'rule-set-id';
@@ -56,20 +56,30 @@ describe('FolderRulesService', () => {
const key = ruleSettingsMock.key; const key = ruleSettingsMock.key;
beforeEach(() => { beforeEach(() => {
apiClientSpy = jasmine.createSpyObj('contentPrivateClient', ['callApi']);
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [NoopTranslateModule], imports: [NoopTranslateModule],
providers: [FolderRulesService, { provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }] providers: [
FolderRulesService,
{
provide: AlfrescoApiService,
useValue: {
getInstance: () => ({
contentPrivateClient: apiClientSpy
})
}
}
]
}); });
folderRulesService = TestBed.inject(FolderRulesService); folderRulesService = TestBed.inject(FolderRulesService);
notificationService = TestBed.inject(NotificationService); notificationService = TestBed.inject(NotificationService);
callApiSpy = spyOn<any>(folderRulesService, 'callApi');
}); });
it('should load some rules into a rule set', () => { it('should load some rules into a rule set', () => {
const ruleSet = ruleSetMock(); const ruleSet = ruleSetMock();
callApiSpy.and.returnValue(of(getRulesResponseMock)); apiClientSpy.callApi.and.returnValue(of(getRulesResponseMock));
expect(ruleSet.rules.length).toBe(0); expect(ruleSet.rules.length).toBe(0);
expect(ruleSet.hasMoreRules).toBeTrue(); expect(ruleSet.hasMoreRules).toBeTrue();
@@ -77,7 +87,17 @@ describe('FolderRulesService', () => {
folderRulesService.loadRules(ruleSet); folderRulesService.loadRules(ruleSet);
expect(callApiSpy).toHaveBeenCalledWith(`/nodes/${ruleSet.owningFolder.id}/rule-sets/${ruleSet.id}/rules?skipCount=0&maxItems=100`, 'GET'); expect(apiClientSpy.callApi).toHaveBeenCalledWith(
`/nodes/${ruleSet.owningFolder.id}/rule-sets/${ruleSet.id}/rules?skipCount=0&maxItems=100`,
'GET',
{},
{},
{},
{},
{},
['application/json'],
['application/json']
);
expect(ruleSet.rules.length).toBe(2); expect(ruleSet.rules.length).toBe(2);
expect(ruleSet.rules).toEqual(rulesMock); expect(ruleSet.rules).toEqual(rulesMock);
expect(ruleSet.hasMoreRules).toBeFalse(); expect(ruleSet.hasMoreRules).toBeFalse();
@@ -85,14 +105,24 @@ describe('FolderRulesService', () => {
it('should load more rules if it still has some more to load', () => { it('should load more rules if it still has some more to load', () => {
const ruleSet = ruleSetMock(rulesMock); const ruleSet = ruleSetMock(rulesMock);
callApiSpy.and.returnValue(of(getMoreRulesResponseMock)); apiClientSpy.callApi.and.returnValue(of(getMoreRulesResponseMock));
expect(ruleSet.rules.length).toBe(2); expect(ruleSet.rules.length).toBe(2);
expect(ruleSet.hasMoreRules).toBeTrue(); expect(ruleSet.hasMoreRules).toBeTrue();
folderRulesService.loadRules(ruleSet); folderRulesService.loadRules(ruleSet);
expect(callApiSpy).toHaveBeenCalledWith(`/nodes/${ruleSet.owningFolder.id}/rule-sets/${ruleSet.id}/rules?skipCount=2&maxItems=100`, 'GET'); expect(apiClientSpy.callApi).toHaveBeenCalledWith(
`/nodes/${ruleSet.owningFolder.id}/rule-sets/${ruleSet.id}/rules?skipCount=2&maxItems=100`,
'GET',
{},
{},
{},
{},
{},
['application/json'],
['application/json']
);
expect(ruleSet.rules.length).toBe(4); expect(ruleSet.rules.length).toBe(4);
expect(ruleSet.rules).toEqual([...rulesMock, ...moreRulesMock]); expect(ruleSet.rules).toEqual([...rulesMock, ...moreRulesMock]);
expect(ruleSet.hasMoreRules).toBeFalse(); expect(ruleSet.hasMoreRules).toBeFalse();
@@ -120,7 +150,9 @@ describe('FolderRulesService', () => {
}); });
it('should delete a rule and return its id', async () => { it('should delete a rule and return its id', async () => {
callApiSpy.withArgs(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules/${ruleId}`, 'DELETE').and.returnValue(ruleId); apiClientSpy.callApi
.withArgs(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules/${ruleId}`, 'DELETE', {}, {}, {}, {}, {}, ['application/json'], ['application/json'])
.and.returnValue(ruleId);
const deletedRulePromise = folderRulesService.deletedRuleId$.pipe(take(2)).toPromise(); const deletedRulePromise = folderRulesService.deletedRuleId$.pipe(take(2)).toPromise();
folderRulesService.deleteRule(nodeId, ruleId, ruleSetId); folderRulesService.deleteRule(nodeId, ruleId, ruleSetId);
@@ -128,13 +160,82 @@ describe('FolderRulesService', () => {
expect(deletedRule).toBeTruthy('rule has not been deleted'); expect(deletedRule).toBeTruthy('rule has not been deleted');
expect(deletedRule).toBe(ruleId, 'wrong id of deleted rule'); expect(deletedRule).toBe(ruleId, 'wrong id of deleted rule');
expect(callApiSpy).toHaveBeenCalledTimes(1); expect(apiClientSpy.callApi).toHaveBeenCalledTimes(1);
expect(callApiSpy).toHaveBeenCalledWith(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules/${ruleId}`, 'DELETE'); expect(apiClientSpy.callApi).toHaveBeenCalledWith(
`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules/${ruleId}`,
'DELETE',
{},
{},
{},
{},
{},
['application/json'],
['application/json']
);
});
it('should emit error when deleting rule fails', (done) => {
const errorMessage = 'Delete failed';
const mockError = new Error(errorMessage);
apiClientSpy.callApi
.withArgs(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules/${ruleId}`, 'DELETE', {}, {}, {}, {}, {}, ['application/json'], ['application/json'])
.and.returnValue(Promise.reject(mockError));
folderRulesService.deletedRuleId$.pipe(take(2)).subscribe((value) => {
if (value !== null) {
expect(value as unknown as Error).toEqual(mockError);
done();
}
});
folderRulesService.deleteRule(nodeId, ruleId, ruleSetId);
});
it('should format simple conditions with default values when properties are missing', () => {
const mockResponse = {
list: {
entries: [
{
entry: {
id: 'test-rule',
name: 'Test Rule',
conditions: {
simpleConditions: [{}]
}
}
}
],
pagination: { hasMoreItems: false }
}
};
apiClientSpy.callApi.and.returnValue(of(mockResponse));
folderRulesService.getRules('folder-id', 'ruleset-id').subscribe((result) => {
const conditions = result.rules[0].conditions.simpleConditions;
expect(conditions[0]).toEqual({
field: 'cm:name',
comparator: 'equals',
parameter: ''
});
});
}); });
it('should send correct POST request and return created rule', async () => { it('should send correct POST request and return created rule', async () => {
callApiSpy apiClientSpy.callApi
.withArgs(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules`, 'POST', mockedRuleWithoutId) .withArgs(
`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules`,
'POST',
{},
{},
{},
{},
mockedRuleWithoutId,
['application/json'],
['application/json']
)
.and.returnValue(Promise.resolve(mockedRuleEntry)); .and.returnValue(Promise.resolve(mockedRuleEntry));
const result = await folderRulesService.createRule(nodeId, mockedRuleWithoutId, ruleSetId); const result = await folderRulesService.createRule(nodeId, mockedRuleWithoutId, ruleSetId);
@@ -142,8 +243,18 @@ describe('FolderRulesService', () => {
}); });
it('should send correct PUT request to update rule and return it', async () => { it('should send correct PUT request to update rule and return it', async () => {
callApiSpy apiClientSpy.callApi
.withArgs(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules/${ruleId}`, 'PUT', mockedRule) .withArgs(
`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules/${ruleId}`,
'PUT',
{},
{},
{},
{},
mockedRule,
['application/json'],
['application/json']
)
.and.returnValue(Promise.resolve(mockedRuleEntry)); .and.returnValue(Promise.resolve(mockedRuleEntry));
const result = await folderRulesService.updateRule(nodeId, ruleId, mockedRule, ruleSetId); const result = await folderRulesService.updateRule(nodeId, ruleId, mockedRule, ruleSetId);
@@ -151,8 +262,18 @@ describe('FolderRulesService', () => {
}); });
it('should display error message and revert enabled state when updating rule fails', async () => { it('should display error message and revert enabled state when updating rule fails', async () => {
callApiSpy apiClientSpy.callApi
.withArgs(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules/${ruleId}`, 'PUT', mockedRule) .withArgs(
`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules/${ruleId}`,
'PUT',
{},
{},
{},
{},
mockedRule,
['application/json'],
['application/json']
)
.and.returnValue(Promise.reject(new Error(JSON.stringify({ error: { briefSummary: 'Error updating rule' } })))); .and.returnValue(Promise.reject(new Error(JSON.stringify({ error: { briefSummary: 'Error updating rule' } }))));
spyOn(notificationService, 'showError'); spyOn(notificationService, 'showError');
@@ -162,16 +283,119 @@ describe('FolderRulesService', () => {
}); });
it('should send correct GET request and return rule settings', async () => { it('should send correct GET request and return rule settings', async () => {
callApiSpy.withArgs(`/nodes/${nodeId}/rule-settings/${key}`, 'GET').and.returnValue(Promise.resolve(mockedRuleSettingsEntry)); apiClientSpy.callApi
.withArgs(`/nodes/${nodeId}/rule-settings/${key}`, 'GET', {}, {}, {}, {}, {}, ['application/json'], ['application/json'])
.and.returnValue(Promise.resolve(mockedRuleSettingsEntry));
const result = await folderRulesService.getRuleSettings(nodeId, key); const result = await folderRulesService.getRuleSettings(nodeId, key);
expect(result).toEqual(ruleSettingsMock); expect(result).toEqual(ruleSettingsMock);
}); });
it('should send correct PUT request to update rule settings and return them', async () => { it('should send correct PUT request to update rule settings and return them', async () => {
callApiSpy.withArgs(`/nodes/${nodeId}/rule-settings/${key}`, 'PUT', ruleSettingsMock).and.returnValue(Promise.resolve(mockedRuleSettingsEntry)); apiClientSpy.callApi
.withArgs(`/nodes/${nodeId}/rule-settings/${key}`, 'PUT', {}, {}, {}, {}, ruleSettingsMock, ['application/json'], ['application/json'])
.and.returnValue(Promise.resolve(mockedRuleSettingsEntry));
const result = await folderRulesService.updateRuleSettings(nodeId, key, ruleSettingsMock); const result = await folderRulesService.updateRuleSettings(nodeId, key, ruleSettingsMock);
expect(result).toEqual(ruleSettingsMock); expect(result).toEqual(ruleSettingsMock);
}); });
it('should handle rule with null simpleConditions', () => {
const mockResponse = {
list: {
entries: [
{
entry: {
id: 'test-rule',
name: 'Test Rule',
conditions: {
simpleConditions: null
}
}
}
],
pagination: { hasMoreItems: false }
}
};
apiClientSpy.callApi.and.returnValue(of(mockResponse));
folderRulesService.getRules('folder-id', 'ruleset-id').subscribe((result) => {
expect(result.rules[0].conditions.simpleConditions).toEqual([]);
});
expect(apiClientSpy.callApi).toHaveBeenCalledWith(
'/nodes/folder-id/rule-sets/ruleset-id/rules?skipCount=0&maxItems=100',
'GET',
{},
{},
{},
{},
{},
['application/json'],
['application/json']
);
});
it('should handle nested composite conditions when getting rules', () => {
const mockResponse = {
list: {
entries: [
{
entry: {
id: 'test-rule',
name: 'Test Rule',
conditions: {
inverted: false,
booleanMode: 'and',
simpleConditions: [],
compositeConditions: [
{
inverted: true,
booleanMode: 'or',
simpleConditions: [{ field: 'cm:title', comparator: 'contains', parameter: 'test' }],
compositeConditions: [
{
inverted: false,
booleanMode: 'and',
simpleConditions: [{ field: 'cm:description', comparator: 'equals', parameter: 'nested' }],
compositeConditions: []
}
]
}
]
}
}
}
],
pagination: { hasMoreItems: false }
}
};
apiClientSpy.callApi.and.returnValue(of(mockResponse));
folderRulesService.getRules('folder-id', 'ruleset-id').subscribe((result) => {
const conditions = result.rules[0].conditions;
expect(conditions.compositeConditions[0].inverted).toBe(true);
expect(conditions.compositeConditions[0].booleanMode).toBe('or');
expect(conditions.compositeConditions[0].compositeConditions[0].inverted).toBe(false);
expect(conditions.compositeConditions[0].compositeConditions[0].booleanMode).toBe('and');
});
});
it('should create empty rule for form with options properties removed', () => {
const result = FolderRulesService.emptyRuleForForm;
expect(result).toEqual({
id: '',
name: '',
description: '',
isShared: false,
triggers: ['inbound'],
conditions: FolderRulesService.emptyCompositeCondition,
actions: [],
options: FolderRulesService.emptyRuleOptions
});
});
}); });