MME-8 Back to observables for edit-image-dialog so that testing works.
This commit is contained in:
parent
7d56c6e17c
commit
352110a372
@ -1,7 +1,7 @@
|
|||||||
<app-dialog-container title="Edit Image">
|
<app-dialog-container title="Edit Image">
|
||||||
@if (imageViewQuery.isLoading()) {
|
@if (loading()) {
|
||||||
<app-spinner></app-spinner>
|
<app-spinner></app-spinner>
|
||||||
} @else if (imageViewQuery.isError()) {
|
} @else if (loadError()) {
|
||||||
<p>There was an error.</p>
|
<p>There was an error.</p>
|
||||||
} @else {
|
} @else {
|
||||||
<form (submit)="onSubmit($event)">
|
<form (submit)="onSubmit($event)">
|
||||||
@ -18,6 +18,9 @@
|
|||||||
<input matInput [formControl]="imageForm.controls.caption" />
|
<input matInput [formControl]="imageForm.controls.caption" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<button type="submit" matButton="filled">Submit</button>
|
<button type="submit" matButton="filled">Submit</button>
|
||||||
|
@if (submitting()) {
|
||||||
|
<app-spinner size="24px"></app-spinner>
|
||||||
|
}
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
</app-dialog-container>
|
</app-dialog-container>
|
||||||
|
|||||||
@ -1,22 +1,68 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
import { EditImageDialog } from './edit-image-dialog';
|
import { EditImageDialog } from './edit-image-dialog';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { Mocked } from 'vitest';
|
||||||
|
import { ImageService } from '../../../../../../shared/services/ImageService';
|
||||||
|
import { ImageView } from '../../../../../../shared/models/ImageView.model';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
describe('EditImageDialog', () => {
|
describe('EditImageDialog', () => {
|
||||||
let component: EditImageDialog;
|
let component: EditImageDialog;
|
||||||
let fixture: ComponentFixture<EditImageDialog>;
|
let fixture: ComponentFixture<EditImageDialog>;
|
||||||
|
let matDialogRef: Partial<Mocked<MatDialogRef<any>>>;
|
||||||
|
|
||||||
|
const username = 'test-user';
|
||||||
|
const filename = 'test-file.jpg';
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
matDialogRef = {
|
||||||
|
close: vi.fn(),
|
||||||
|
} as Partial<Mocked<MatDialogRef<any>>>;
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [EditImageDialog],
|
imports: [EditImageDialog],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: MAT_DIALOG_DATA,
|
||||||
|
useValue: { username, filename },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MatDialogRef,
|
||||||
|
useValue: matDialogRef,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ImageService,
|
||||||
|
useValue: {
|
||||||
|
getImageView2: vi.fn((username, filename) =>
|
||||||
|
of({
|
||||||
|
filename,
|
||||||
|
owner: {
|
||||||
|
username,
|
||||||
|
},
|
||||||
|
} as ImageView),
|
||||||
|
),
|
||||||
|
updateImage: vi.fn(() => of({} as ImageView)),
|
||||||
|
} as Partial<Mocked<ImageService>>,
|
||||||
|
},
|
||||||
|
],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(EditImageDialog);
|
fixture = TestBed.createComponent(EditImageDialog);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should close dialog after successful submit', async () => {
|
||||||
|
const formDebug = fixture.debugElement.query(By.css('form'));
|
||||||
|
expect(formDebug).toBeTruthy();
|
||||||
|
const formElement = formDebug.nativeElement as HTMLFormElement;
|
||||||
|
formElement.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
|
||||||
|
expect(matDialogRef.close).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { Component, inject, OnInit } from '@angular/core';
|
import { Component, inject, OnInit, signal } from '@angular/core';
|
||||||
import { DialogContainer } from '../../../../../../shared/components/dialog-container/dialog-container';
|
import { DialogContainer } from '../../../../../../shared/components/dialog-container/dialog-container';
|
||||||
import { MatFormField, MatInput, MatLabel } from '@angular/material/input';
|
import { MatFormField, MatInput, MatLabel } from '@angular/material/input';
|
||||||
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { ImageView } from '../../../../../../shared/models/ImageView.model';
|
import { ImageView } from '../../../../../../shared/models/ImageView.model';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { MatButton } from '@angular/material/button';
|
import { MatButton } from '@angular/material/button';
|
||||||
import { injectMutation, injectQuery } from '@tanstack/angular-query-experimental';
|
|
||||||
import { ImageService } from '../../../../../../shared/services/ImageService';
|
import { ImageService } from '../../../../../../shared/services/ImageService';
|
||||||
import { notNullOrUndefined } from '../../../../../../shared/util';
|
import { notNullOrUndefined } from '../../../../../../shared/util';
|
||||||
import { Spinner } from '../../../../../../shared/components/spinner/spinner';
|
import { Spinner } from '../../../../../../shared/components/spinner/spinner';
|
||||||
@ -21,11 +20,12 @@ export class EditImageDialog implements OnInit {
|
|||||||
private readonly dialogRef = inject(MatDialogRef);
|
private readonly dialogRef = inject(MatDialogRef);
|
||||||
private readonly imageService = inject(ImageService);
|
private readonly imageService = inject(ImageService);
|
||||||
|
|
||||||
protected readonly imageViewQuery = injectQuery(() =>
|
protected readonly loading = signal(false);
|
||||||
this.imageService.getImageView(this.usernameFilename[0], this.usernameFilename[1]),
|
protected readonly loadError = signal<Error | null>(null);
|
||||||
);
|
protected readonly imageView = signal<ImageView | null>(null);
|
||||||
|
|
||||||
private readonly imageViewMutation = injectMutation(() => this.imageService.updateImage2());
|
protected readonly submitting = signal(false);
|
||||||
|
protected readonly submitError = signal<Error | null>(null);
|
||||||
|
|
||||||
protected readonly imageForm = new FormGroup({
|
protected readonly imageForm = new FormGroup({
|
||||||
filename: new FormControl(
|
filename: new FormControl(
|
||||||
@ -40,18 +40,27 @@ export class EditImageDialog implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
this.imageViewQuery.promise().then((imageView) => {
|
this.loading.set(true);
|
||||||
this.imageForm.patchValue({
|
this.imageService.getImageView2(this.usernameFilename[0], this.usernameFilename[1]).subscribe({
|
||||||
filename: imageView.filename,
|
next: (imageView) => {
|
||||||
alt: imageView.alt,
|
this.loading.set(false); // shouldn't need this
|
||||||
caption: imageView.caption,
|
this.imageView.set(imageView);
|
||||||
});
|
this.imageForm.patchValue({
|
||||||
|
filename: imageView.filename,
|
||||||
|
alt: imageView.alt,
|
||||||
|
caption: imageView.caption,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: (e) => {
|
||||||
|
this.loading.set(false);
|
||||||
|
this.loadError.set(e);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onSubmit(event: SubmitEvent): Promise<void> {
|
protected async onSubmit(event: SubmitEvent): Promise<void> {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const imageView: ImageView = this.imageViewQuery.data()!;
|
const imageView = this.imageView()!;
|
||||||
const formValue = this.imageForm.value;
|
const formValue = this.imageForm.value;
|
||||||
if (notNullOrUndefined(formValue.alt)) {
|
if (notNullOrUndefined(formValue.alt)) {
|
||||||
imageView.alt = formValue.alt;
|
imageView.alt = formValue.alt;
|
||||||
@ -59,10 +68,17 @@ export class EditImageDialog implements OnInit {
|
|||||||
if (notNullOrUndefined(formValue.caption)) {
|
if (notNullOrUndefined(formValue.caption)) {
|
||||||
imageView.caption = formValue.caption;
|
imageView.caption = formValue.caption;
|
||||||
}
|
}
|
||||||
this.imageViewMutation.mutate(imageView, {
|
this.submitting.set(true);
|
||||||
onSuccess: () => {
|
this.imageService.updateImage(imageView.owner.username, imageView.filename, imageView).subscribe({
|
||||||
|
next: (imageView) => {
|
||||||
|
this.submitting.set(false);
|
||||||
|
this.imageView.set(imageView);
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
},
|
},
|
||||||
|
error: (e) => {
|
||||||
|
this.submitting.set(false);
|
||||||
|
this.submitError.set(e);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/app/shared/bodies/ImageUpdateBody.ts
Normal file
8
src/app/shared/bodies/ImageUpdateBody.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export interface ImageUpdateBody {
|
||||||
|
alt?: string | null;
|
||||||
|
caption?: string | null;
|
||||||
|
isPublic?: boolean | null;
|
||||||
|
viewersToAdd?: string[] | null;
|
||||||
|
viewersToRemove?: string[] | null;
|
||||||
|
clearAllViewers?: boolean | null;
|
||||||
|
}
|
||||||
@ -1,13 +1,14 @@
|
|||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { firstValueFrom, map, tap } from 'rxjs';
|
import { firstValueFrom, map, Observable, tap } from 'rxjs';
|
||||||
import { EndpointService } from './EndpointService';
|
import { EndpointService } from './EndpointService';
|
||||||
import { ImageView } from '../models/ImageView.model';
|
import { ImageView } from '../models/ImageView.model';
|
||||||
import { SliceView } from '../models/SliceView.model';
|
import { SliceView } from '../models/SliceView.model';
|
||||||
import { QueryParams } from '../models/Query.model';
|
import { QueryParams } from '../models/Query.model';
|
||||||
import { mutationOptions, QueryClient, QueryOptions, queryOptions } from '@tanstack/angular-query-experimental';
|
import { QueryClient, QueryOptions, queryOptions } from '@tanstack/angular-query-experimental';
|
||||||
import { ImageViewWithBlobUrl } from '../client-models/ImageViewWithBlobUrl';
|
import { ImageViewWithBlobUrl } from '../client-models/ImageViewWithBlobUrl';
|
||||||
import { WithStringDates } from '../util';
|
import { WithStringDates } from '../util';
|
||||||
|
import { ImageUpdateBody } from '../bodies/ImageUpdateBody';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@ -74,18 +75,10 @@ export class ImageService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getImageView(username: string, filename: string) {
|
public getImageView2(username: string, filename: string): Observable<ImageView> {
|
||||||
return queryOptions({
|
return this.httpClient
|
||||||
queryKey: ['image-views', username, filename],
|
.get<WithStringDates<ImageView>>(this.endpointService.getUrl('images', [username, filename, 'view']))
|
||||||
queryFn: () =>
|
.pipe(map((withStringDates) => this.hydrateImageView(withStringDates)));
|
||||||
firstValueFrom(
|
|
||||||
this.httpClient
|
|
||||||
.get<
|
|
||||||
WithStringDates<ImageView>
|
|
||||||
>(this.endpointService.getUrl('images', [username, filename, 'view']))
|
|
||||||
.pipe(map((rawImageView) => this.hydrateImageView(rawImageView))),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public uploadImage(
|
public uploadImage(
|
||||||
@ -142,47 +135,9 @@ export class ImageService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateImage(
|
public updateImage(username: string, filename: string, data: ImageUpdateBody): Observable<ImageView> {
|
||||||
username: string,
|
return this.httpClient
|
||||||
filename: string,
|
.put<WithStringDates<ImageView>>(this.endpointService.getUrl('images', [username, filename]), data)
|
||||||
data: {
|
.pipe(map((withStringDates) => this.hydrateImageView(withStringDates)));
|
||||||
alt?: string | null;
|
|
||||||
caption?: string | null;
|
|
||||||
isPublic?: boolean | null;
|
|
||||||
viewersToAdd?: string[] | null;
|
|
||||||
viewersToRemove?: string[] | null;
|
|
||||||
clearAllViewers?: boolean | null;
|
|
||||||
},
|
|
||||||
): Promise<ImageView> {
|
|
||||||
return firstValueFrom(
|
|
||||||
this.httpClient.put<ImageView>(this.endpointService.getUrl('images', [username, filename]), data).pipe(
|
|
||||||
tap(async () => {
|
|
||||||
await this.queryClient.refetchQueries({
|
|
||||||
queryKey: ['image-views', username, filename],
|
|
||||||
});
|
|
||||||
await this.queryClient.refetchQueries({
|
|
||||||
queryKey: ['image-views-with-blob-urls', username, filename],
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateImage2() {
|
|
||||||
return mutationOptions({
|
|
||||||
mutationKey: ['image-views'],
|
|
||||||
mutationFn: (imageView: ImageView) =>
|
|
||||||
firstValueFrom(
|
|
||||||
this.httpClient.put(
|
|
||||||
this.endpointService.getUrl('images', [imageView.owner.username, imageView.filename]),
|
|
||||||
imageView,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onSuccess: async () => {
|
|
||||||
await this.queryClient.invalidateQueries({
|
|
||||||
queryKey: ['image-views'],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user