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">
|
||||
@if (imageViewQuery.isLoading()) {
|
||||
@if (loading()) {
|
||||
<app-spinner></app-spinner>
|
||||
} @else if (imageViewQuery.isError()) {
|
||||
} @else if (loadError()) {
|
||||
<p>There was an error.</p>
|
||||
} @else {
|
||||
<form (submit)="onSubmit($event)">
|
||||
@ -18,6 +18,9 @@
|
||||
<input matInput [formControl]="imageForm.controls.caption" />
|
||||
</mat-form-field>
|
||||
<button type="submit" matButton="filled">Submit</button>
|
||||
@if (submitting()) {
|
||||
<app-spinner size="24px"></app-spinner>
|
||||
}
|
||||
</form>
|
||||
}
|
||||
</app-dialog-container>
|
||||
|
||||
@ -1,22 +1,68 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { By } from '@angular/platform-browser';
|
||||
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', () => {
|
||||
let component: EditImageDialog;
|
||||
let fixture: ComponentFixture<EditImageDialog>;
|
||||
let matDialogRef: Partial<Mocked<MatDialogRef<any>>>;
|
||||
|
||||
const username = 'test-user';
|
||||
const filename = 'test-file.jpg';
|
||||
|
||||
beforeEach(async () => {
|
||||
matDialogRef = {
|
||||
close: vi.fn(),
|
||||
} as Partial<Mocked<MatDialogRef<any>>>;
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
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();
|
||||
|
||||
fixture = TestBed.createComponent(EditImageDialog);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
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 { MatFormField, MatInput, MatLabel } from '@angular/material/input';
|
||||
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { ImageView } from '../../../../../../shared/models/ImageView.model';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { MatButton } from '@angular/material/button';
|
||||
import { injectMutation, injectQuery } from '@tanstack/angular-query-experimental';
|
||||
import { ImageService } from '../../../../../../shared/services/ImageService';
|
||||
import { notNullOrUndefined } from '../../../../../../shared/util';
|
||||
import { Spinner } from '../../../../../../shared/components/spinner/spinner';
|
||||
@ -21,11 +20,12 @@ export class EditImageDialog implements OnInit {
|
||||
private readonly dialogRef = inject(MatDialogRef);
|
||||
private readonly imageService = inject(ImageService);
|
||||
|
||||
protected readonly imageViewQuery = injectQuery(() =>
|
||||
this.imageService.getImageView(this.usernameFilename[0], this.usernameFilename[1]),
|
||||
);
|
||||
protected readonly loading = signal(false);
|
||||
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({
|
||||
filename: new FormControl(
|
||||
@ -40,18 +40,27 @@ export class EditImageDialog implements OnInit {
|
||||
});
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.imageViewQuery.promise().then((imageView) => {
|
||||
this.imageForm.patchValue({
|
||||
filename: imageView.filename,
|
||||
alt: imageView.alt,
|
||||
caption: imageView.caption,
|
||||
});
|
||||
this.loading.set(true);
|
||||
this.imageService.getImageView2(this.usernameFilename[0], this.usernameFilename[1]).subscribe({
|
||||
next: (imageView) => {
|
||||
this.loading.set(false); // shouldn't need this
|
||||
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();
|
||||
const imageView: ImageView = this.imageViewQuery.data()!;
|
||||
const imageView = this.imageView()!;
|
||||
const formValue = this.imageForm.value;
|
||||
if (notNullOrUndefined(formValue.alt)) {
|
||||
imageView.alt = formValue.alt;
|
||||
@ -59,10 +68,17 @@ export class EditImageDialog implements OnInit {
|
||||
if (notNullOrUndefined(formValue.caption)) {
|
||||
imageView.caption = formValue.caption;
|
||||
}
|
||||
this.imageViewMutation.mutate(imageView, {
|
||||
onSuccess: () => {
|
||||
this.submitting.set(true);
|
||||
this.imageService.updateImage(imageView.owner.username, imageView.filename, imageView).subscribe({
|
||||
next: (imageView) => {
|
||||
this.submitting.set(false);
|
||||
this.imageView.set(imageView);
|
||||
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 { HttpClient } from '@angular/common/http';
|
||||
import { firstValueFrom, map, tap } from 'rxjs';
|
||||
import { firstValueFrom, map, Observable, tap } from 'rxjs';
|
||||
import { EndpointService } from './EndpointService';
|
||||
import { ImageView } from '../models/ImageView.model';
|
||||
import { SliceView } from '../models/SliceView.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 { WithStringDates } from '../util';
|
||||
import { ImageUpdateBody } from '../bodies/ImageUpdateBody';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@ -74,18 +75,10 @@ export class ImageService {
|
||||
});
|
||||
}
|
||||
|
||||
public getImageView(username: string, filename: string) {
|
||||
return queryOptions({
|
||||
queryKey: ['image-views', username, filename],
|
||||
queryFn: () =>
|
||||
firstValueFrom(
|
||||
this.httpClient
|
||||
.get<
|
||||
WithStringDates<ImageView>
|
||||
>(this.endpointService.getUrl('images', [username, filename, 'view']))
|
||||
.pipe(map((rawImageView) => this.hydrateImageView(rawImageView))),
|
||||
),
|
||||
});
|
||||
public getImageView2(username: string, filename: string): Observable<ImageView> {
|
||||
return this.httpClient
|
||||
.get<WithStringDates<ImageView>>(this.endpointService.getUrl('images', [username, filename, 'view']))
|
||||
.pipe(map((withStringDates) => this.hydrateImageView(withStringDates)));
|
||||
}
|
||||
|
||||
public uploadImage(
|
||||
@ -142,47 +135,9 @@ export class ImageService {
|
||||
);
|
||||
}
|
||||
|
||||
public updateImage(
|
||||
username: string,
|
||||
filename: string,
|
||||
data: {
|
||||
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'],
|
||||
});
|
||||
},
|
||||
});
|
||||
public updateImage(username: string, filename: string, data: ImageUpdateBody): Observable<ImageView> {
|
||||
return this.httpClient
|
||||
.put<WithStringDates<ImageView>>(this.endpointService.getUrl('images', [username, filename]), data)
|
||||
.pipe(map((withStringDates) => this.hydrateImageView(withStringDates)));
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user