Skip to content
Snippets Groups Projects
Commit 5d1faeb6 authored by OZGCloud's avatar OZGCloud
Browse files

Merge branch 'master' into OZG-5243-E2E-Fixes

parents 27011f3a 4bb3726b
No related branches found
No related tags found
No related merge requests found
Showing
with 441 additions and 328 deletions
......@@ -56,6 +56,13 @@ services:
- GRPC_CLIENT_INFO-MANAGER_NEGOTIATIONTYPE=PLAINTEXT
- GRPC_CLIENT_COMMAND-MANAGER_NEGOTIATIONTYPE=PLAINTEXT
- GRPC_SERVER_SECURITY_ENABLED=false
# Bescheid-Wizard
- OZGCLOUD_FEATURE_BESCHEID_ENABLE-DUMMY-DOCUMENT-PROCESSOR=false
- OZGCLOUD_BESCHEID_SMART_DOCUMENTS_URL=http://smocker:8080/smartdocuments
- OZGCLOUD_BESCHEID_SMART_DOCUMENTS_BASIC_AUTH_USERNAME=MGM
- OZGCLOUD_BESCHEID_SMART_DOCUMENTS_BASIC_AUTH_PASSWORD=MGM
- OZGCLOUD_BESCHEID_SMART_DOCUMENTS_TEMPLATE_GROUP=OzgCloudTest
- OZGCLOUD_BESCHEID_SMART_DOCUMENTS_TEMPLATE=Halteverbot
ports:
- 9091:9090
depends_on:
......@@ -87,6 +94,10 @@ services:
- LOGGING_CONFIG=classpath:log4j2-local.xml
- BPL_DEBUG_ENABLED=true
- BPL_DEBUG_PORT=5000
# bescheid-wizard
- OZGCLOUD_FEATURE_BESCHEID-WIZARD=true
- OZGCLOUD_VORGANG_BESCHEID_0_FORM_ENGINE_NAME=FormSolutions
- OZGCLOUD_VORGANG_BESCHEID_0_FORM_ID=KFAS_STAGE_KI_10_Haltverbot_LANDESHACKATHON
ports:
- 8080:8080
- 5000:5000
......
......@@ -63,7 +63,7 @@
placeholder="Betreff hier eingeben"
variant="error"
>
<ods-error-message error="Betreff fehlt"></ods-error-message
<ods-error-message text="Betreff fehlt"></ods-error-message
></ods-text-input>
</div>
<div class="my-4">
......@@ -75,7 +75,7 @@
placeholder="Nachrichtentext hier eingeben"
variant="error"
>
<ods-error-message error="Nachrichtentext fehlt"></ods-error-message>
<ods-error-message text="Nachrichtentext fehlt"></ods-error-message>
</ods-textarea>
</div>
......
......@@ -11,12 +11,12 @@ import {
} from '@alfa-client/command-shared';
import {
ApiError,
createEmptyStateResource,
createErrorStateResource,
createStateResource,
EMPTY_STRING,
HttpError,
StateResource,
createEmptyStateResource,
createErrorStateResource,
createStateResource,
} from '@alfa-client/tech-shared';
import { Mock, mock, useFromMock } from '@alfa-client/test-utils';
import {
......@@ -27,12 +27,12 @@ import {
} from '@alfa-client/vorgang-shared';
import { fakeAsync, tick } from '@angular/core/testing';
import faker from '@faker-js/faker';
import { getUrl, ResourceUri } from '@ngxp/rest';
import { ResourceUri, getUrl } from '@ngxp/rest';
import { cold } from 'jest-marbles';
import { CommandLinkRel } from 'libs/command-shared/src/lib/command.linkrel';
import { createApiError } from 'libs/tech-shared/test/error';
import { createVorgangWithEingangResource } from 'libs/vorgang-shared/test/vorgang';
import { first, Observable, of } from 'rxjs';
import { Observable, first, of } from 'rxjs';
import {
createBinaryFileListResource,
createBinaryFileResource,
......@@ -114,15 +114,15 @@ describe('BescheidService', () => {
createStateResource(bescheidDraft);
it('should call resource service', () => {
service.bescheidDraftService.get = jest.fn();
service.bescheidResourceService.get = jest.fn();
service.getBescheidDraft();
expect(service.bescheidDraftService.get).toHaveBeenCalled();
expect(service.bescheidResourceService.get).toHaveBeenCalled();
});
it('should return value', () => {
service.bescheidDraftService.get = jest
service.bescheidResourceService.get = jest
.fn()
.mockReturnValue(singleCold(bescheidDraftStateResource));
......@@ -148,7 +148,7 @@ describe('BescheidService', () => {
beforeEach(() => {
facade.getBescheidCommand.mockReturnValue(of(commandStateResource));
service.bescheidDraftService.setResourceByUri = jest.fn();
service.bescheidResourceService.loadByResourceUri = jest.fn();
});
it('should call facade', () => {
......@@ -163,7 +163,7 @@ describe('BescheidService', () => {
it('should set resource by uri', () => {
service.createBescheid(vorgangWithEingang).pipe(first()).subscribe();
expect(service.bescheidDraftService.setResourceByUri).toHaveBeenCalledWith(
expect(service.bescheidResourceService.loadByResourceUri).toHaveBeenCalledWith(
getUrl(command, CommandLinkRel.EFFECTED_RESOURCE),
);
});
......@@ -368,8 +368,9 @@ describe('BescheidService', () => {
});
it('should return command', () => {
const command$: Observable<StateResource<CommandResource>> =
service.bescheidErstellungUeberspringen(vorgangWithEingangResource);
const command$: Observable<StateResource<CommandResource>> = service.vorgangAbschliesen(
vorgangWithEingangResource,
);
expect(command$).toBeObservable(cold('(a|)', { a: commandStateResource }));
});
......@@ -419,16 +420,16 @@ describe('BescheidService', () => {
buildUpdateBescheidCommandPropsSpy = jest
.spyOn(BescheidUtil, 'buildUpdateBescheidCommandProps')
.mockReturnValue(createCommandProps);
service.bescheidDraftService.stateResource.next(createStateResource(bescheidResource));
commandService.createCommandByProps.mockReturnValue(of(commandStateResource));
service.bescheidDraftService.setResourceByUri = jest.fn();
service.bescheidResourceService.loadByResourceUri = jest.fn();
service.getResource = jest.fn().mockReturnValue(createBescheidResource());
});
it('should build update bescheid command props', () => {
service.updateBescheid(bescheid);
expect(buildUpdateBescheidCommandPropsSpy).toHaveBeenCalledWith(
service.bescheidDraftService.getResource(),
service.getResource(),
bescheid,
);
});
......@@ -451,7 +452,7 @@ describe('BescheidService', () => {
.updateBescheid(bescheid)
.pipe(first())
.subscribe((commandStateResource: StateResource<CommandResource>) => {
expect(service.bescheidDraftService.setResourceByUri).toHaveBeenCalledWith(
expect(service.bescheidResourceService.loadByResourceUri).toHaveBeenCalledWith(
getUrl(commandStateResource.resource, CommandLinkRel.EFFECTED_RESOURCE),
);
done();
......@@ -486,7 +487,7 @@ describe('BescheidService', () => {
let buildSendBescheidCommandPropsSpy: jest.SpyInstance;
beforeEach(() => {
service.bescheidDraftService.get = jest
service.bescheidResourceService.get = jest
.fn()
.mockReturnValue(of(createStateResource(bescheidResource)));
buildSendBescheidCommandPropsSpy = jest
......@@ -498,7 +499,7 @@ describe('BescheidService', () => {
it('should get resource', () => {
service.sendBescheid(bescheidResource, linkRel);
expect(service.bescheidDraftService.get).toHaveBeenCalled();
expect(service.bescheidResourceService.get).toHaveBeenCalled();
});
it('should call command service', (done) => {
......@@ -956,7 +957,7 @@ describe('BescheidService', () => {
beforeEach(() => {
commandService.createCommandByProps.mockReturnValue(of(commandStateResource));
service.bescheidDraftService.getResource = jest.fn().mockReturnValue(bescheidResource);
service.getResource = jest.fn().mockReturnValue(bescheidResource);
buildCreateBescheidDocumentCommandPropsSpy = jest
.spyOn(BescheidUtil, 'buildCreateBescheidDocumentCommandProps')
.mockReturnValue(createCommandProps);
......@@ -1087,12 +1088,28 @@ describe('BescheidService', () => {
});
describe('exists bescheid draft', () => {
it('should call resource service', () => {
service.bescheidDraftService.exists = jest.fn();
beforeEach(() => {
service.bescheidResourceService.existResource = jest.fn().mockReturnValue(of(true));
});
it('should call bescheid resource service', () => {
service.existsBescheidDraft();
expect(service.bescheidDraftService.exists).toHaveBeenCalled();
expect(service.bescheidResourceService.existResource).toHaveBeenCalled();
});
it('should return false on missing resource', () => {
service.bescheidResourceService.existResource = jest.fn().mockReturnValue(of(false));
const exists: boolean = service.existsBescheidDraft();
expect(exists).toBeFalsy();
});
it('should return true on existing resource', () => {
const exists: boolean = service.existsBescheidDraft();
expect(exists).toBeTruthy();
});
});
......@@ -1103,14 +1120,14 @@ describe('BescheidService', () => {
beforeEach(() => {
service.deleteBescheid = jest.fn().mockReturnValue(of(commandStateResource));
service.deleteBescheidDocument = jest.fn();
service.getResource = jest.fn().mockReturnValue(createBescheidResource());
});
it('should get resource', () => {
service.bescheidDraftService.getResource = jest.fn();
service.bescheidVerwerfen().pipe(first()).subscribe();
expect(service.bescheidDraftService.getResource).toHaveBeenCalled();
expect(service.getResource).toHaveBeenCalled();
});
it('should delete bescheid', (done) => {
......@@ -1182,19 +1199,19 @@ describe('BescheidService', () => {
});
describe('get bescheid list', () => {
it('should call bescheid list service', () => {
service.bescheidListService.getList = jest.fn();
it('should call bescheid list resource service', () => {
service.bescheidListResourceService.getList = jest.fn();
service.getBescheidList();
expect(service.bescheidListService.getList).toHaveBeenCalled();
expect(service.bescheidListResourceService.getList).toHaveBeenCalled();
});
it('should return value', () => {
const bescheidList: BescheidListResource = createBescheidListResource();
const bescheidListStateResource: StateResource<BescheidListResource> =
createStateResource(bescheidList);
service.bescheidListService.getList = jest
service.bescheidListResourceService.getList = jest
.fn()
.mockReturnValue(singleCold(bescheidListStateResource));
......@@ -1228,6 +1245,32 @@ describe('BescheidService', () => {
});
});
describe('get resource', () => {
const bescheidResource: BescheidResource = createBescheidResource();
const bescheidStateResource: StateResource<BescheidResource> =
createStateResource(bescheidResource);
it('should call bescheid resource service select resource', () => {
service.bescheidResourceService.selectResource = jest
.fn()
.mockReturnValue(of(bescheidStateResource));
service.getResource();
expect(service.bescheidResourceService.selectResource).toHaveBeenCalled();
});
it('should return value', () => {
service.bescheidResourceService.selectResource = jest
.fn()
.mockReturnValue(of(bescheidStateResource));
const bescheidDraft: BescheidResource = service.getResource();
expect(bescheidDraft).toBe(bescheidResource);
});
});
describe('getEmpfaenger', () => {
it('should return Empfänger', () => {
const vorgangWithEingangResource: VorgangWithEingangResource =
......@@ -1258,7 +1301,9 @@ describe('BescheidService', () => {
sortByGermanDateStrSpy = jest
.spyOn(DateUtil, 'sortByGermanDateStr')
.mockReturnValue(bescheide);
getItemsSpy = service.bescheidListService.getItems = jest.fn().mockReturnValue(of(bescheide));
getItemsSpy = service.bescheidListResourceService.getItems = jest
.fn()
.mockReturnValue(of(bescheide));
});
it('should get items', () => {
......@@ -1297,7 +1342,9 @@ describe('BescheidService', () => {
beforeEach(() => {
service.getBescheidList = jest.fn().mockReturnValue(of(bescheidListStateResource));
service.filterBySentStatus = jest.fn().mockReturnValue(bescheide);
getItemsSpy = service.bescheidListService.getItems = jest.fn().mockReturnValue(of(bescheide));
getItemsSpy = service.bescheidListResourceService.getItems = jest
.fn()
.mockReturnValue(of(bescheide));
});
it('should get items', () => {
......@@ -1356,12 +1403,12 @@ describe('BescheidService', () => {
});
describe('refresh list', () => {
it('should call refresh on list service', () => {
service.bescheidListService.refresh = jest.fn();
it('should call refresh on list resource service', () => {
service.bescheidListResourceService.refresh = jest.fn();
service.refreshList();
expect(service.bescheidListService.refresh).toHaveBeenCalled();
expect(service.bescheidListResourceService.refresh).toHaveBeenCalled();
});
});
......
......@@ -13,17 +13,17 @@ import {
tapOnCommandSuccessfullyDone,
} from '@alfa-client/command-shared';
import {
HttpError,
ResourceListService,
StateResource,
createEmptyStateResource,
createStateResource,
filterIsLoadedOrHasError,
getEmbeddedResources,
hasStateResourceError,
HttpError,
isLoaded,
isNotEmpty,
ResourceListService,
sortByGermanDateStr,
StateResource,
} from '@alfa-client/tech-shared';
import {
VorgangCommandService,
......@@ -33,16 +33,17 @@ import {
} from '@alfa-client/vorgang-shared';
import { getEmpfaenger } from '@alfa-client/vorgang-shared-ui';
import { Injectable } from '@angular/core';
import { getUrl, hasLink, ResourceUri } from '@ngxp/rest';
import { ResourceUri, getUrl, hasLink } from '@ngxp/rest';
import {
BehaviorSubject,
Observable,
Subscription,
filter,
first,
map,
Observable,
startWith,
Subscription,
switchMap,
take,
tap,
} from 'rxjs';
import {
......@@ -73,8 +74,8 @@ import { DocumentResource } from './document.model';
@Injectable({ providedIn: 'root' })
export class BescheidService {
bescheidDraftService: ResourceService<VorgangWithEingangResource, BescheidResource>;
bescheidListService: ResourceListService<
bescheidResourceService: ResourceService<VorgangWithEingangResource, BescheidResource>;
bescheidListResourceService: ResourceListService<
VorgangWithEingangResource,
BescheidListResource,
BescheidResource
......@@ -116,12 +117,12 @@ export class BescheidService {
private readonly binaryFileService: BinaryFileService,
private readonly repository: ResourceRepository,
) {
this.bescheidDraftService = new CommandResourceService(
this.bescheidResourceService = new CommandResourceService(
this.buildBescheidDraftServiceConfig(),
repository,
this.commandService,
);
this.bescheidListService = new ResourceListService(
this.bescheidListResourceService = new ResourceListService(
this.buildBescheidListServiceConfig(),
repository,
);
......@@ -145,7 +146,7 @@ export class BescheidService {
}
public init(): void {
this.bescheidDraftService = new CommandResourceService(
this.bescheidResourceService = new CommandResourceService(
this.buildBescheidDraftServiceConfig(),
this.repository,
this.commandService,
......@@ -158,7 +159,7 @@ export class BescheidService {
}
public getBescheidDraft(): Observable<StateResource<BescheidResource>> {
return this.bescheidDraftService.get();
return this.bescheidResourceService.get();
}
public getBescheidCommand(): Observable<StateResource<CommandResource>> {
......@@ -223,7 +224,7 @@ export class BescheidService {
}
public updateBescheid(bescheid: Bescheid): Observable<StateResource<CommandResource>> {
return this.doUpdateBescheid(this.bescheidDraftService.getResource(), bescheid).pipe(
return this.doUpdateBescheid(this.getResource(), bescheid).pipe(
tapOnCommandSuccessfullyDone((commandStateResource: StateResource<CommandResource>) => {
this.updateBescheidDraft(commandStateResource.resource);
this.clearCreateBescheidDocumentInProgress();
......@@ -248,7 +249,7 @@ export class BescheidService {
bescheidResource: BescheidResource,
linkRel: string,
): Observable<StateResource<CommandResource>> {
return this.bescheidDraftService.get().pipe(
return this.bescheidResourceService.get().pipe(
filterIsLoadedOrHasError(),
switchMap((stateResource: StateResource<BescheidResource>) =>
this.commandService.createCommandByProps(
......@@ -268,7 +269,7 @@ export class BescheidService {
}
private updateBescheidDraft(command: CommandResource): void {
this.bescheidDraftService.setResourceByUri(getEffectedResourceUrl(command));
this.bescheidResourceService.loadByResourceUri(getEffectedResourceUrl(command));
}
public getAttachments(): Observable<BinaryFileResource[]> {
......@@ -422,7 +423,7 @@ export class BescheidService {
doCreateBescheidDocument(): Observable<StateResource<CommandResource>> {
return this.commandService.createCommandByProps(
buildCreateBescheidDocumentCommandProps(this.bescheidDraftService.getResource()),
buildCreateBescheidDocumentCommandProps(this.getResource()),
);
}
......@@ -452,11 +453,16 @@ export class BescheidService {
}
public existsBescheidDraft(): boolean {
return this.bescheidDraftService.exists();
let exists: boolean;
this.bescheidResourceService
.existResource()
.pipe(take(1))
.subscribe((existsDraft: boolean) => (exists = existsDraft));
return exists;
}
public bescheidVerwerfen(): Observable<StateResource<CommandResource>> {
return this.deleteBescheid(this.bescheidDraftService.getResource()).pipe(
return this.deleteBescheid(this.getResource()).pipe(
tapOnCommandSuccessfullyDone(() => {
this.deleteBescheidDocument();
this.vorgangService.reloadCurrentVorgang();
......@@ -468,6 +474,19 @@ export class BescheidService {
return this.commandService.createCommandByProps(buildDeleteBescheidCommandProps(bescheid));
}
/**
* @returns @deprecated Don't use this function, instead pass data to function if necessarry.
*/
getResource(): BescheidResource {
let resource: StateResource<BescheidResource> = undefined;
this.bescheidResourceService
.selectResource()
.pipe(take(1))
.subscribe((stateResource: StateResource<BescheidResource>) => (resource = stateResource));
return resource.resource;
}
public reloadCurrentVorgang(): void {
this.vorgangService.reloadCurrentVorgang();
}
......@@ -490,7 +509,7 @@ export class BescheidService {
}
public getLastBescheid(): Observable<BescheidResource> {
return this.bescheidListService.getItems().pipe(
return this.bescheidListResourceService.getItems().pipe(
map((bescheide: BescheidResource[]) => this.filterBySentStatus(bescheide)),
map((bescheide: BescheidResource[]) => this.sortByBeschiedenAm(bescheide)),
map((bescheide: BescheidResource[]) => bescheide[0]),
......@@ -505,14 +524,14 @@ export class BescheidService {
}
public existBescheid(): Observable<boolean> {
return this.bescheidListService.getItems().pipe(
return this.bescheidListResourceService.getItems().pipe(
map((bescheide: BescheidResource[]) => this.filterBySentStatus(bescheide)),
map((bescheide: BescheidResource[]) => isNotEmpty(bescheide)),
);
}
public getBescheidList(): Observable<StateResource<BescheidListResource>> {
return this.bescheidListService.getList();
return this.bescheidListResourceService.getList();
}
filterBySentStatus(bescheide: BescheidResource[]): BescheidResource[] {
......@@ -524,7 +543,7 @@ export class BescheidService {
}
public refreshList(): void {
this.bescheidListService.refresh();
this.bescheidListResourceService.refresh();
}
public uploadAttachment(
......
......@@ -65,14 +65,6 @@ describe('CommandResourceService', () => {
service.stateResource.next(stateResourceWithDeleteLink);
});
it('should throw error if delete linkRel not exists on current stateresource', () => {
service.stateResource.next(createStateResource(createDummyResource()));
expect(() => service.delete()).toThrowError(
'No delete link exists on current stateresource.',
);
});
it('should call command service', () => {
service.delete();
......
......@@ -32,7 +32,6 @@ export class CommandResourceService<B extends Resource, T extends Resource> exte
}
public delete(): Observable<StateResource<CommandResource>> {
this.verifyDeleteLinkRel();
return this.commandService.createCommandByProps(this.buildDeleteCommandProps());
}
......
......@@ -2,7 +2,7 @@ import { StateResource, createEmptyStateResource } from '@alfa-client/tech-share
import { CommonModule } from '@angular/common';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Resource } from '@ngxp/rest';
import { ButtonComponent, buttonVariants } from '@ods/system';
import { ButtonComponent, ErrorMessageComponent, buttonVariants } from '@ods/system';
import { VariantProps } from 'class-variance-authority';
import { isNil } from 'lodash-es';
......@@ -11,8 +11,15 @@ type ButtonVariants = VariantProps<typeof buttonVariants>;
@Component({
selector: 'ods-button-with-spinner',
standalone: true,
imports: [CommonModule, ButtonComponent],
template: `<ods-button
imports: [CommonModule, ButtonComponent, ErrorMessageComponent],
styles: [':host {@apply flex flex-col}'],
template: `<ods-error-message
*ngIf="stateResource.error"
text="Ein Fehler ist aufgetreten."
subText="Versuchen Sie es nocheinmal."
>
</ods-error-message>
<ods-button
[text]="text"
[variant]="variant"
[size]="size"
......
<ng-container *ngFor="let issue of issues"
><ods-error-message [error]="message(issue)"></ods-error-message
><ods-error-message [text]="message(issue)"></ods-error-message
></ng-container>
......@@ -13,6 +13,7 @@ export * from './lib/icons/attachment-icon/attachment-icon.component';
export * from './lib/icons/bescheid-generate-icon/bescheid-generate-icon.component';
export * from './lib/icons/bescheid-upload-icon/bescheid-upload-icon.component';
export * from './lib/icons/close-icon/close-icon.component';
export * from './lib/icons/exclamation-icon/exclamation-icon.component';
export * from './lib/icons/file-icon/file-icon.component';
export * from './lib/icons/save-icon/save-icon.component';
export * from './lib/icons/send-icon/send-icon.component';
......
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ExclamationIconComponent } from '../../icons/exclamation-icon/exclamation-icon.component';
import { ErrorMessageComponent } from './error-message.component';
describe('ErrorMessageComponent', () => {
......@@ -7,7 +8,7 @@ describe('ErrorMessageComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ErrorMessageComponent],
imports: [ErrorMessageComponent, ExclamationIconComponent],
}).compileComponents();
fixture = TestBed.createComponent(ErrorMessageComponent);
......
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { ExclamationIconComponent } from '../../icons/exclamation-icon/exclamation-icon.component';
@Component({
selector: 'ods-error-message',
standalone: true,
imports: [CommonModule],
template: `<p *ngIf="error" error class="text-error mt-2 text-sm">{{ error }}</p>`,
imports: [CommonModule, ExclamationIconComponent],
styles: [':host {@apply flex text-error my-2 text-sm items-center font-medium}'],
template: `<ods-exclamation-icon class="mr-1"></ods-exclamation-icon>
<div class="flex-grow break-all">
{{ text }}
<br *ngIf="subText" aria-hidden="true" />
{{ subText }}
</div> `,
})
export class ErrorMessageComponent {
@Input() error: string;
@Input({ required: true }) text!: string;
@Input() subText: string = '';
}
import { moduleMetadata, type Meta, type StoryObj } from '@storybook/angular';
import { argsToTemplate, moduleMetadata, type Meta, type StoryObj } from '@storybook/angular';
import { ErrorMessageComponent } from './error-message.component';
const meta: Meta<ErrorMessageComponent> = {
......@@ -19,6 +18,16 @@ type Story = StoryObj<ErrorMessageComponent>;
export const Default: Story = {
args: {
error: 'Text of error',
text: 'Ein Fehler ist aufgetreten.',
subText: 'Versuchen Sie es nocheinmal.',
},
argTypes: {
text: { description: 'First line of text' },
subText: { description: 'Second line of text' },
},
render: (args: ErrorMessageComponent) => ({
props: args,
template: `<ods-error-message ${argsToTemplate(args)} class="w-72">
</ods-error-message>`,
}),
};
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ExclamationIconComponent } from './exclamation-icon.component';
describe('ExclamationIconComponent', () => {
let component: ExclamationIconComponent;
let fixture: ComponentFixture<ExclamationIconComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ExclamationIconComponent],
}).compileComponents();
fixture = TestBed.createComponent(ExclamationIconComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { NgClass } from '@angular/common';
import { Component, Input } from '@angular/core';
import { twMerge } from 'tailwind-merge';
import { IconVariants, iconVariants } from '../IconClasses';
@Component({
selector: 'ods-exclamation-icon',
standalone: true,
imports: [NgClass],
template: `<svg
xmlns="http://www.w3.org/2000/svg"
[ngClass]="[twMerge(iconVariants({ size }), 'fill-error', class)]"
aria-hidden="true"
viewBox="0 0 20 20"
fill="inherit"
>
<path
d="M10.3069 14.7308C10.5416 14.7308 10.7383 14.6533 10.8971 14.4985C11.0559 14.3437 11.1353 14.1518 11.1353 13.923C11.1353 13.6942 11.0559 13.5023 10.8971 13.3475C10.7383 13.1928 10.5416 13.1155 10.3069 13.1155C10.0722 13.1155 9.87548 13.1928 9.71668 13.3475C9.55789 13.5023 9.47849 13.6942 9.47849 13.923C9.47849 14.1518 9.55789 14.3437 9.71668 14.4985C9.87548 14.6533 10.0722 14.7308 10.3069 14.7308ZM10.3072 11.077C10.5253 11.077 10.7079 11.0051 10.8551 10.8613C11.0024 10.7176 11.0761 10.5395 11.0761 10.327V5.827C11.0761 5.6145 11.0023 5.43633 10.8548 5.2925C10.7073 5.14883 10.5246 5.077 10.3066 5.077C10.0885 5.077 9.9059 5.14883 9.75873 5.2925C9.61139 5.43633 9.53772 5.6145 9.53772 5.827V10.327C9.53772 10.5395 9.61148 10.7176 9.75899 10.8613C9.9065 11.0051 10.0892 11.077 10.3072 11.077ZM10.3087 19.5C8.96109 19.5 7.69442 19.2507 6.50868 18.752C5.32295 18.2533 4.29156 17.5766 3.41452 16.7218C2.53748 15.8669 1.84308 14.8617 1.33132 13.706C0.81973 12.5503 0.563934 11.3156 0.563934 10.0017C0.563934 8.68775 0.819644 7.45267 1.33106 6.2965C1.84248 5.14033 2.53654 4.13467 3.41324 3.2795C4.28994 2.42433 5.3209 1.74725 6.50612 1.24825C7.69134 0.749417 8.95767 0.5 10.3051 0.5C11.6527 0.5 12.9194 0.749333 14.1051 1.248C15.2909 1.74667 16.3222 2.42342 17.1993 3.27825C18.0763 4.13308 18.7707 5.13833 19.2825 6.294C19.7941 7.44967 20.0499 8.68442 20.0499 9.99825C20.0499 11.3123 19.7942 12.5473 19.2827 13.7035C18.7713 14.8597 18.0773 15.8653 17.2006 16.7205C16.3239 17.5757 15.2929 18.2528 14.1077 18.7518C12.9225 19.2506 11.6561 19.5 10.3087 19.5Z"
/>
</svg>`,
})
export class ExclamationIconComponent {
@Input() size: IconVariants['size'] = 'medium';
@Input() class: string = undefined;
iconVariants = iconVariants;
twMerge = twMerge;
}
import type { Meta, StoryObj } from '@storybook/angular';
import { ExclamationIconComponent } from './exclamation-icon.component';
const meta: Meta<ExclamationIconComponent> = {
title: 'Icons/Exclamation icon',
component: ExclamationIconComponent,
excludeStories: /.*Data$/,
tags: ['autodocs'],
};
export default meta;
type Story = StoryObj<ExclamationIconComponent>;
export const Default: Story = {
args: { size: 'medium' },
argTypes: {
size: {
control: 'select',
options: ['small', 'medium', 'large', 'extra-large', 'full'],
description: 'Size of icon. Property "full" means 100%',
table: {
defaultValue: { summary: 'medium' },
},
},
},
};
......@@ -46,14 +46,6 @@ describe('ApiResourceService', () => {
const resourceWithEditLinkRel: Resource = createDummyResource([editLinkRel]);
it('should throw error if edit link not exists', () => {
service.stateResource.next(createStateResource(createDummyResource()));
expect(() => service.save(dummyToSave)).toThrowError(
'No edit link exists on current stateresource.',
);
});
it('should call repository', fakeAsync(() => {
service.stateResource.next(createStateResource(resourceWithEditLinkRel));
repository.save.mockReturnValue(of(loadedResource));
......@@ -99,37 +91,4 @@ describe('ApiResourceService', () => {
expect(service.stateResource.value).toEqual(createStateResource(loadedResource));
}));
});
describe('delete', () => {
const resourceWithDeleteLinkRel: Resource = createDummyResource([deleteLinkRel]);
const stateResourceWithDeleteLink: StateResource<Resource> =
createStateResource(resourceWithDeleteLinkRel);
beforeEach(() => {
service.stateResource.next(stateResourceWithDeleteLink);
});
it('should throw error if delete linkRel not exists on current stateresource', () => {
service.stateResource.next(createStateResource(createDummyResource()));
expect(() => service.delete()).toThrowError(
'No delete link exists on current stateresource.',
);
});
it('should call repository', () => {
service.delete();
expect(repository.delete).toHaveBeenCalledWith(resourceWithDeleteLinkRel, deleteLinkRel);
});
it('should return value', () => {
const deleteResource: Resource = createDummyResource();
repository.delete.mockReturnValue(singleHot(deleteResource));
const deletedResource: Observable<Resource> = service.delete();
expect(deletedResource).toBeObservable(singleCold(deleteResource));
});
});
});
......@@ -22,9 +22,4 @@ export class ApiResourceService<B extends Resource, T extends Resource> extends
toSave,
});
}
public delete(): Observable<Resource> {
this.verifyDeleteLinkRel();
return this.repository.delete(this.getResource(), this.config.delete.linkRel);
}
}
......@@ -11,6 +11,7 @@ import { createDummyResource } from '../../../test/resource';
import { HttpError, ProblemDetail } from '../tech.model';
import { LinkRelationName, ResourceServiceConfig } from './resource.model';
import { ResourceRepository } from './resource.repository';
import { ResourceService } from './resource.service';
import {
StateResource,
createEmptyStateResource,
......@@ -18,7 +19,6 @@ import {
createStateResource,
} from './resource.util';
import { ResourceService } from './resource.service';
import * as ResourceUtil from './resource.util';
describe('ResourceService', () => {
......@@ -31,6 +31,9 @@ describe('ResourceService', () => {
const configStateResource: StateResource<Resource> = createStateResource(configResource);
const configStateResource$: Observable<StateResource<Resource>> = of(configStateResource);
const dummyResource: Resource = createDummyResource();
const dummyStateResource: StateResource<Resource> = createStateResource(dummyResource);
const editLinkRel: string = 'dummyEditLinkRel';
const getLinkRel: LinkRelationName = 'dummyGetLinkRel';
const deleteLinkRel: LinkRelationName = 'dummyDeleteLinkRel';
......@@ -58,8 +61,7 @@ describe('ResourceService', () => {
beforeEach(() => {
service.stateResource.next(stateResource);
service.handleNullConfigResource = jest.fn();
service.handleConfigResourceChanged = jest.fn();
service.handleResourceChanges = jest.fn();
isInvalidResourceCombinationSpy = jest
.spyOn(ResourceUtil, 'isInvalidResourceCombination')
.mockReturnValue(true);
......@@ -69,17 +71,7 @@ describe('ResourceService', () => {
service.get().subscribe();
tick();
expect(service.handleConfigResourceChanged).toHaveBeenCalledWith(
stateResource,
configResource,
);
}));
it('should handle null configresource', fakeAsync(() => {
service.get().subscribe();
tick();
expect(service.handleNullConfigResource).toHaveBeenCalledWith(configResource);
expect(service.handleResourceChanges).toHaveBeenCalledWith(stateResource, configResource);
}));
it('should call isInvalidResourceCombinationSpy', fakeAsync(() => {
......@@ -102,25 +94,21 @@ describe('ResourceService', () => {
});
});
describe('handle config resource changed', () => {
describe('handle resource changes', () => {
const stateResource: StateResource<Resource> = createStateResource(createDummyResource());
const changedConfigResource: Resource = createDummyResource();
describe('on different config resource', () => {
it('should update config resource if is different', () => {
service.configResource = createDummyResource();
service.handleConfigResourceChanged(stateResource, changedConfigResource);
expect(service.configResource).toBe(changedConfigResource);
beforeEach(() => {
service.handleConfigResourceChanges = jest.fn();
});
it('should set state resource reload', () => {
it('should update state resource by config resource', () => {
service.configResource = createDummyResource();
service.handleConfigResourceChanged(stateResource, changedConfigResource);
service.handleResourceChanges(stateResource, changedConfigResource);
expect(service.stateResource.value.reload).toBeTruthy();
expect(service.handleConfigResourceChanges).toHaveBeenCalled();
});
});
......@@ -134,7 +122,7 @@ describe('ResourceService', () => {
it('should call shouldLoadResource', () => {
service.shouldLoadResource = jest.fn();
service.handleConfigResourceChanged(stateResource, configResource);
service.handleResourceChanges(stateResource, configResource);
expect(service.shouldLoadResource).toHaveBeenCalledWith(stateResource, configResource);
});
......@@ -143,73 +131,139 @@ describe('ResourceService', () => {
service.shouldLoadResource = jest.fn().mockReturnValue(true);
service.loadResource = jest.fn();
service.handleConfigResourceChanged(stateResource, configResource);
service.handleResourceChanges(stateResource, configResource);
expect(service.loadResource).toHaveBeenCalledWith(configResource);
});
it('should NOT load resource on shouldLoadResource false', () => {
it('should NOT load resource', () => {
service.loadResource = jest.fn();
service.shouldLoadResource = jest.fn().mockReturnValue(false);
service.handleConfigResourceChanged(stateResource, configResource);
service.handleResourceChanges(stateResource, configResource);
expect(service.loadResource).not.toHaveBeenCalled();
});
});
});
describe('handle null config resource', () => {
const resource: Resource = createDummyResource();
const stateResource: StateResource<Resource> = createStateResource(resource);
describe('handle config resource changes', () => {
const stateResource: StateResource<Resource> = createStateResource(createDummyResource());
it('should update configresource', () => {
service.configResource = createDummyResource();
service.handleResourceChanges(stateResource, configResource);
expect(service.configResource).toBe(configResource);
});
describe('on stable stateresource', () => {
beforeEach(() => {
service.shouldClearStateResource = jest.fn();
service.stateResource.next(stateResource);
jest.spyOn(ResourceUtil, 'isStateResoureStable').mockReturnValue(true);
service.updateStateResourceByConfigResource = jest.fn();
});
it('should call shouldClearStateResource', () => {
service.handleNullConfigResource(null);
expect(service.shouldClearStateResource).toHaveBeenCalledWith(null);
it('should update stateresource by configresource', () => {
service.handleResourceChanges(stateResource, configResource);
expect(service.updateStateResourceByConfigResource).toHaveBeenCalledWith(
stateResource,
configResource,
);
});
});
it('should clear stateresource if shouldClearStateResource is true', () => {
describe('on instable stateresource', () => {
beforeEach(() => {
jest.spyOn(ResourceUtil, 'isStateResoureStable').mockReturnValue(false);
});
it('should NOT update stateresource by configresource', () => {
service.updateStateResourceByConfigResource = jest.fn();
service.handleResourceChanges(stateResource, configResource);
expect(service.updateStateResourceByConfigResource).not.toHaveBeenCalled();
});
});
});
describe('update stateresource by configresource', () => {
it('should check if should clear stateresource', () => {
service.shouldClearStateResource = jest.fn();
service.updateStateResourceByConfigResource(dummyStateResource, configResource);
expect(service.shouldClearStateResource).toHaveBeenCalled();
});
it('should clear resource if should', () => {
service.stateResource.next(createStateResource(createDummyResource()));
service.shouldClearStateResource = jest.fn().mockReturnValue(true);
service.handleNullConfigResource(null);
service.updateStateResourceByConfigResource(dummyStateResource, configResource);
expect(service.stateResource.value).toEqual(createEmptyStateResource());
});
it('should keep stateresource if shouldClearStateResource is false', () => {
describe('on NOT clearing stateresource', () => {
beforeEach(() => {
service.shouldClearStateResource = jest.fn().mockReturnValue(false);
});
service.handleNullConfigResource(null);
it('should load resource if link exists', () => {
service.hasGetLink = jest.fn();
expect(service.stateResource.value).toBe(stateResource);
service.updateStateResourceByConfigResource(dummyStateResource, configResource);
expect(service.hasGetLink).toHaveBeenCalledWith(configResource);
});
});
});
describe('should clear stateresource', () => {
const resource: Resource = createDummyResource();
const stateResource: StateResource<Resource> = createStateResource(resource);
describe('on existing stateresource', () => {
beforeEach(() => {
service.stateResource.next(dummyStateResource);
});
it('should return true on null configresource and filled stateresource', () => {
service.stateResource.next(stateResource);
it('should return true if configresource is null', () => {
const shouldClear: boolean = service.shouldClearStateResource(dummyStateResource, null);
const shouldClear: boolean = service.shouldClearStateResource(null);
expect(shouldClear).toBeTruthy();
});
it('should return true if configresource has no get link', () => {
const shouldClear: boolean = service.shouldClearStateResource(
dummyStateResource,
createDummyResource(),
);
expect(shouldClear).toBeTruthy();
});
});
it('should return false on null configresource and empty stateresource', () => {
service.stateResource.next(createEmptyStateResource());
describe('on empty stateresource', () => {
it('should return false', () => {
const shouldClear: boolean = service.shouldClearStateResource(
createEmptyStateResource(),
null,
);
expect(shouldClear).toBeFalsy();
});
const shouldClear: boolean = service.shouldClearStateResource(null);
it('should return false if configresource has no get link', () => {
const shouldClear: boolean = service.shouldClearStateResource(
createEmptyStateResource(),
createDummyResource(),
);
expect(shouldClear).toBeFalsy();
});
});
});
describe('should load resource', () => {
const resource: Resource = createDummyResource();
......@@ -247,12 +301,6 @@ describe('ResourceService', () => {
describe('load resource', () => {
const configResourceWithGetLinkRel: Resource = createDummyResource([getLinkRel]);
it('should throw error if getLinkRel not exists', () => {
expect(() => service.loadResource(configResource)).toThrowError(
'No get link exists on configresource.',
);
});
it('should call do load resource', () => {
service.doLoadResource = jest.fn();
......@@ -314,12 +362,12 @@ describe('ResourceService', () => {
});
});
describe('setResourceByUri', () => {
describe('loadByResourceUri', () => {
it('should do load resource', () => {
service.doLoadResource = jest.fn();
const resourceUri: ResourceUri = faker.internet.url();
service.setResourceByUri(resourceUri);
service.loadByResourceUri(resourceUri);
expect(service.doLoadResource).toHaveBeenCalledWith(resourceUri);
});
......@@ -343,14 +391,6 @@ describe('ResourceService', () => {
const resourceWithEditLinkRel: Resource = createDummyResource([editLinkRel]);
it('should throw error if edit link not exists', () => {
service.stateResource.next(createStateResource(createDummyResource()));
expect(() => service.save(dummyToSave)).toThrowError(
'No edit link exists on current stateresource.',
);
});
it('should do save', fakeAsync(() => {
const stateResource: StateResource<Resource> = createStateResource(resourceWithEditLinkRel);
service.stateResource.next(stateResource);
......@@ -434,77 +474,31 @@ describe('ResourceService', () => {
});
});
describe('can edit', () => {
it('should return true if link is present', () => {
const resource: StateResource<Resource> = createStateResource(
createDummyResource([editLinkRel]),
);
service.stateResource.next(resource);
const canEdit: boolean = service.canEdit();
expect(canEdit).toBeTruthy();
});
it('should return false if link is NOT present', () => {
const resource: StateResource<Resource> = createStateResource(createDummyResource());
service.stateResource.next(resource);
const canEdit: boolean = service.canEdit();
expect(canEdit).toBeFalsy();
});
});
describe('can delete', () => {
it('should return true if link is present', () => {
const resource: StateResource<Resource> = createStateResource(
createDummyResource([deleteLinkRel]),
);
service.stateResource.next(resource);
const canEdit: boolean = service.canDelete();
expect(canEdit).toBeTruthy();
});
it('should return false if link is NOT present', () => {
const resource: StateResource<Resource> = createStateResource(createDummyResource());
service.stateResource.next(resource);
describe('exist resource', () => {
it('should return true on existing resource', () => {
service.stateResource.next(createStateResource(createDummyResource()));
const canEdit: boolean = service.canDelete();
const existResource$: Observable<boolean> = service.existResource();
expect(canEdit).toBeFalsy();
});
expect(existResource$).toBeObservable(singleCold(true));
});
describe('get resource', () => {
it('should return resource from stateResource', () => {
const resource: Resource = createDummyResource();
const stateResource: StateResource<Resource> = createStateResource(resource);
service.stateResource.next(stateResource);
it('should return false on null resource', () => {
service.stateResource.next(createEmptyStateResource());
const result: Resource = service.getResource();
const existResource$: Observable<boolean> = service.existResource();
expect(result).toBe(resource);
});
expect(existResource$).toBeObservable(singleCold(false));
});
describe('exists', () => {
it('should return true', () => {
service.updateStateResource(createDummyResource());
const exists = service.exists();
expect(exists).toBeTruthy();
});
it('should return false', () => {
service.updateStateResource(null);
describe('select resource', () => {
it('should return state resource', () => {
service.stateResource.next(dummyStateResource);
const exists = service.exists();
const resource$: Observable<StateResource<Resource>> = service.selectResource();
expect(exists).toBeFalsy();
expect(resource$).toBeObservable(singleCold(dummyStateResource));
});
});
});
......
......@@ -26,8 +26,8 @@ import {
createStateResource,
isInvalidResourceCombination,
isLoadingRequired,
isStateResoureStable,
StateResource,
throwErrorOn,
} from './resource.util';
/**
......@@ -39,7 +39,7 @@ export abstract class ResourceService<B extends Resource, T extends Resource> {
createEmptyStateResource(),
);
configResource: B;
configResource: B = null;
constructor(
protected config: ResourceServiceConfig<B>,
......@@ -49,9 +49,8 @@ export abstract class ResourceService<B extends Resource, T extends Resource> {
public get(): Observable<StateResource<T>> {
return combineLatest([this.stateResource.asObservable(), this.getConfigResource()]).pipe(
tap(([stateResource, configResource]) =>
this.handleConfigResourceChanged(stateResource, configResource),
this.handleResourceChanges(stateResource, configResource),
),
tap(([, configResource]) => this.handleNullConfigResource(configResource)),
filter(
([stateResource]) => !isInvalidResourceCombination(stateResource, this.configResource),
),
......@@ -70,33 +69,61 @@ export abstract class ResourceService<B extends Resource, T extends Resource> {
);
}
handleConfigResourceChanged(stateResource: StateResource<T>, configResource: B): void {
handleResourceChanges(stateResource: StateResource<T>, configResource: B): void {
if (!isEqual(this.configResource, configResource)) {
this.configResource = configResource;
this.stateResource.next({ ...this.stateResource.value, reload: true });
this.handleConfigResourceChanges(stateResource, configResource);
} else if (this.shouldLoadResource(stateResource, configResource)) {
this.loadResource(configResource);
}
}
handleConfigResourceChanges(stateResource: StateResource<T>, configResource: B) {
this.configResource = configResource;
if (isStateResoureStable(stateResource)) {
this.updateStateResourceByConfigResource(stateResource, configResource);
}
}
updateStateResourceByConfigResource(stateResource: StateResource<T>, configResource: B): void {
if (this.shouldClearStateResource(stateResource, configResource)) {
this.clearResource();
} else if (this.hasGetLink(configResource)) {
this.loadResource(configResource);
}
}
shouldClearStateResource(stateResource: StateResource<T>, configResource: B): boolean {
return (
(isNull(configResource) || this.hasNotGetLink(configResource)) &&
!this.isStateResourceEmpty(stateResource)
);
}
private hasNotGetLink(configResource: B): boolean {
return !this.hasGetLink(configResource);
}
private isStateResourceEmpty(stateResource: StateResource<T>): boolean {
return isEqual(stateResource, createEmptyStateResource());
}
private clearResource(): void {
this.stateResource.next(createEmptyStateResource());
}
hasGetLink(configResource: B): boolean {
return isNotNull(configResource) && hasLink(configResource, this.config.getLinkRel);
}
shouldLoadResource(stateResource: StateResource<T>, configResource: B): boolean {
return isNotNull(configResource) && isLoadingRequired(stateResource);
}
loadResource(configResource: B): void {
this.verifyGetLink(configResource);
this.doLoadResource(getUrl(configResource, this.config.getLinkRel));
}
private verifyGetLink(configResource: B): void {
throwErrorOn(
!hasLink(configResource, this.config.getLinkRel),
'No get link exists on configresource.',
);
}
//TODO rename to reloadByResourceUri
public setResourceByUri(resourceUri: ResourceUri): void {
public loadByResourceUri(resourceUri: ResourceUri): void {
this.doLoadResource(resourceUri);
}
......@@ -116,18 +143,7 @@ export abstract class ResourceService<B extends Resource, T extends Resource> {
this.stateResource.next(createStateResource(resource));
}
handleNullConfigResource(configResource: B): void {
if (this.shouldClearStateResource(configResource)) {
this.stateResource.next(createEmptyStateResource());
}
}
shouldClearStateResource(configResource: B): boolean {
return isNull(configResource) && !isEqual(this.stateResource.value, createEmptyStateResource());
}
public save(toSave: unknown): Observable<StateResource<T | HttpError>> {
this.verifyEditLinkRel();
const previousResource: T = this.stateResource.value.resource;
return this.doSave(previousResource, toSave).pipe(
tap((loadedResource: T) => this.stateResource.next(createStateResource(loadedResource))),
......@@ -143,52 +159,17 @@ export abstract class ResourceService<B extends Resource, T extends Resource> {
return throwError(() => errorResponse);
}
public verifyEditLinkRel(): void {
throwErrorOn(
!this.hasLinkRel(this.config.edit.linkRel),
'No edit link exists on current stateresource.',
);
}
abstract doSave(resource: T, toSave: unknown): Observable<T>;
public refresh(): void {
this.stateResource.next({ ...this.stateResource.value, reload: true });
}
/**
* @deprecated
*/
public canEdit(): boolean {
return this.hasLinkRel(this.config.edit.linkRel);
}
protected hasLinkRel(linkRel: string): boolean {
return hasLink(this.getResource(), linkRel);
public existResource(): Observable<boolean> {
return this.stateResource.asObservable().pipe(mapToResource<T>(), map(isNotNull));
}
/**
* @deprecated
*/
public getResource(): T {
return this.stateResource.value.resource;
}
/**
* @deprecated
*/
public canDelete(): boolean {
return this.hasLinkRel(this.config.delete.linkRel);
}
protected verifyDeleteLinkRel(): void {
throwErrorOn(!this.canDelete(), 'No delete link exists on current stateresource.');
}
/**
* @deprecated
*/
public exists(): boolean {
return isNotNull(this.stateResource.value.resource);
public selectResource(): Observable<StateResource<T>> {
return this.stateResource.asObservable();
}
}
......@@ -74,6 +74,19 @@ function getDebugElementFromFixtureByDirective(
return fixture.debugElement.query(By.directive(query));
}
function getAllDebugElementsFromFixtureByDirective(
fixture: ComponentFixture<any>,
query: Type<any>,
): DebugElement[] {
return fixture.debugElement.queryAll(By.directive(query));
}
export function getMockComponent<T>(fixture: ComponentFixture<unknown>, component: Type<T>): T {
return <T>getDebugElementFromFixtureByDirective(fixture, component).componentInstance;
}
export function getMockComponents<T>(fixture: ComponentFixture<unknown>, component: Type<T>): T[] {
return getAllDebugElementsFromFixtureByDirective(fixture, component).map(
(debugElement) => <T>debugElement.componentInstance,
);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment