mplus-studio / src /app /app.integration.spec.ts
joelsefanja's picture
Deploy Mplus Studio
993eeba verified
Raw
History Blame Contribute Delete
6.74 kB
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { App } from './app';
describe('App studio flow', () => {
let fixture: ComponentFixture<App>;
beforeEach(async () => {
vi.stubGlobal('crypto', { randomUUID: vi.fn(() => `image-${Math.random()}`) });
vi.stubGlobal('fetch', vi.fn());
vi.stubGlobal('URL', {
createObjectURL: vi.fn((file: File) => `blob:${file.name}`),
revokeObjectURL: vi.fn(),
});
await TestBed.configureTestingModule({
imports: [App],
}).compileComponents();
fixture = TestBed.createComponent(App);
fixture.detectChanges();
});
afterEach(() => {
vi.unstubAllGlobals();
});
it('shows the empty starting state', () => {
expect(text()).toContain('Maak een menu-afbeelding.');
expect(fixture.nativeElement.querySelector('.primary').disabled).toBe(true);
expect(fixture.nativeElement.querySelectorAll('.image-card')).toHaveLength(0);
});
it('loads multiple images from the picker without showing result cards yet', () => {
selectFiles([
new File(['one'], 'broodje.png', { type: 'image/png' }),
new File(['two'], 'koffie.jpg', { type: 'image/jpeg' }),
new File(['skip'], 'artikelen.csv', { type: 'text/csv' }),
]);
expect(text()).not.toContain('artikelen.csv');
expect(fixture.nativeElement.querySelectorAll('.image-card')).toHaveLength(0);
expect(fixture.nativeElement.querySelector('.primary').disabled).toBe(false);
});
it('updates settings, generates variants and shows the generated result', async () => {
mockCompletedGenerationJob('job-1', {
images: [
{
originalName: 'cola.webp',
variants: [
{
label: 'FLUX.2 Flex',
imageBase64: 'data:image/png;base64,generated',
},
],
},
],
});
selectFiles([new File(['one'], 'cola.webp', { type: 'image/webp' })]);
const selects = fixture.nativeElement.querySelectorAll('select') as NodeListOf<HTMLSelectElement>;
selects[0].value = 'premium';
selects[0].dispatchEvent(new Event('change'));
selects[1].value = '9:16';
selects[1].dispatchEvent(new Event('change'));
const textarea = fixture.nativeElement.querySelector('textarea') as HTMLTextAreaElement;
textarea.value = 'Maak een realistische koeling achtergrond';
textarea.dispatchEvent(new Event('input'));
fixture.detectChanges();
click('.primary');
expect(text()).toContain('Beelden maken...');
await waitForGeneration();
fixture.detectChanges();
expect(text()).toContain('Maak nieuwe beelden');
expect(text()).toContain('cola.webp');
expect(text()).toContain('9:16');
expect(text()).toContain('FLUX.2 Flex beeld is klaar voor controle.');
expect(fixture.nativeElement.querySelector('.generated img').src).toBe('data:image/png;base64,generated');
expect(fetch).toHaveBeenCalledWith('/api/generations', expect.objectContaining({ method: 'POST' }));
expect(fetch).toHaveBeenCalledWith('/api/generations/job-1');
});
it('submits menu mode with multiple products', async () => {
mockCompletedGenerationJob('job-menu', {
images: [
{
originalName: 'burger.jpg',
variants: [{ label: 'Menu deal', imageBase64: 'data:image/png;base64,menu' }],
},
],
});
selectFiles([
new File(['burger'], 'burger.jpg', { type: 'image/jpeg' }),
new File(['cola'], 'cola-bottle.jpg', { type: 'image/jpeg' }),
new File(['fries'], 'fries.jpg', { type: 'image/jpeg' }),
]);
click('.primary');
await waitForGeneration();
fixture.detectChanges();
const form = vi.mocked(fetch).mock.calls[0][1]?.body as FormData;
expect(form.get('mode')).toBe('menu');
expect(form.getAll('images')).toHaveLength(3);
expect(text()).toContain('Menu deal beeld is klaar voor controle.');
});
it('shows an error when backend generation fails', async () => {
vi.mocked(fetch).mockResolvedValue(new Response(undefined, { status: 502 }));
selectFiles([new File(['one'], 'cola.webp', { type: 'image/webp' })]);
click('.primary');
await waitForGeneration();
fixture.detectChanges();
expect(text()).toContain('De beeldgenerator kon het verzoek niet verwerken:');
});
it('shows an error when an async job fails', async () => {
vi.mocked(fetch).mockResolvedValueOnce(new Response(
JSON.stringify({ jobId: 'job-failed', provider: 'kaggle', status: 'queued' }),
{ status: 202, headers: { 'Content-Type': 'application/json' } },
));
vi.mocked(fetch).mockResolvedValueOnce(new Response(
JSON.stringify({ jobId: 'job-failed', provider: 'kaggle', status: 'failed', error: 'Kaggle failed' }),
{ status: 200, headers: { 'Content-Type': 'application/json' } },
));
selectFiles([new File(['one'], 'cola.webp', { type: 'image/webp' })]);
click('.primary');
await waitForGeneration();
fixture.detectChanges();
expect(text()).toContain('De beeldgenerator kon het verzoek niet verwerken: Kaggle failed');
});
it('keeps selected images hidden until a result is generated', () => {
selectFiles([new File(['one'], 'pizza.png', { type: 'image/png' })]);
expect(text()).not.toContain('pizza.png');
expect(fixture.nativeElement.querySelectorAll('.image-card')).toHaveLength(0);
});
function mockCompletedGenerationJob(jobId: string, result: object): void {
vi.mocked(fetch).mockResolvedValueOnce(new Response(
JSON.stringify({ jobId, provider: 'kaggle', status: 'queued' }),
{ status: 202, headers: { 'Content-Type': 'application/json' } },
));
vi.mocked(fetch).mockResolvedValueOnce(new Response(
JSON.stringify({ jobId, provider: 'kaggle', status: 'completed', result }),
{ status: 200, headers: { 'Content-Type': 'application/json' } },
));
}
function selectFiles(files: File[]): void {
const input = <HTMLInputElement>fixture.nativeElement.querySelector('input[type="file"]');
Object.defineProperty(input, 'files', {
configurable: true,
value: files,
});
input.dispatchEvent(new Event('change'));
fixture.detectChanges();
}
function click(selector: string): void {
fixture.debugElement.query(By.css(selector)).nativeElement.click();
fixture.detectChanges();
}
function text(): string {
return fixture.nativeElement.textContent.replace(/\s+/g, ' ').trim();
}
async function waitForGeneration(): Promise<void> {
await fixture.whenStable();
await new Promise((resolve) => window.setTimeout(resolve));
await fixture.whenStable();
}
});