diff --git a/src/components/app/pixel-forge-app.ts b/src/components/app/pixel-forge-app.ts index 2d009e1..3fea758 100644 --- a/src/components/app/pixel-forge-app.ts +++ b/src/components/app/pixel-forge-app.ts @@ -244,6 +244,7 @@ export class PixelForgeApp extends BaseComponent { @state() timelineHeight = 200; @state() private isResizingTimeline = false; @state() private hasLibraryProject = false; + @state() private projectSelectionRequired = false; @state() private warningMessage: string | null = null; private resizeStartY = 0; @@ -347,7 +348,7 @@ export class PixelForgeApp extends BaseComponent { private handlePaintByNumberDialogClose = () => { this.showPaintByNumberDialog = false; - if (!this.hasLibraryProject) { + if (this.projectSelectionRequired) { this.showProjectBrowser = true; } }; @@ -355,7 +356,7 @@ export class PixelForgeApp extends BaseComponent { private handleNewProjectDialogClose = () => { this.showNewProjectDialog = false; - if (!this.hasLibraryProject) { + if (this.projectSelectionRequired) { this.showProjectBrowser = true; } }; @@ -400,18 +401,20 @@ export class PixelForgeApp extends BaseComponent { }; private handleProjectBrowserClose = () => { - if (this.hasLibraryProject) { + if (!this.projectSelectionRequired) { this.showProjectBrowser = false; } }; private handleProjectOpened = () => { this.hasLibraryProject = true; + this.projectSelectionRequired = false; this.showProjectBrowser = false; }; private handleProjectCreated = () => { this.hasLibraryProject = true; + this.projectSelectionRequired = false; this.showNewProjectDialog = false; this.showPaintByNumberDialog = false; this.showProjectBrowser = false; @@ -419,6 +422,7 @@ export class PixelForgeApp extends BaseComponent { private handleCurrentProjectDeleted = () => { this.hasLibraryProject = false; + this.projectSelectionRequired = true; this.showProjectBrowser = true; }; @@ -462,6 +466,7 @@ export class PixelForgeApp extends BaseComponent { (await workspaceStore.restoreWorkspace(workspaceState)) ) { this.hasLibraryProject = true; + this.projectSelectionRequired = false; this.showProjectBrowser = false; return; } @@ -482,15 +487,18 @@ export class PixelForgeApp extends BaseComponent { ) { historyStore.clear(); this.hasLibraryProject = true; + this.projectSelectionRequired = false; this.showProjectBrowser = false; } else { this.hasLibraryProject = false; - this.showProjectBrowser = true; + this.projectSelectionRequired = false; + this.showProjectBrowser = false; } } catch (error) { log.warn("Failed to load saved project, starting fresh:", error); this.hasLibraryProject = false; - this.showProjectBrowser = true; + this.projectSelectionRequired = false; + this.showProjectBrowser = false; } } @@ -681,7 +689,7 @@ export class PixelForgeApp extends BaseComponent { ${this.showProjectBrowser ? html` { expect(element.showProjectBrowser).toBe(false); }); + it('starts in the editor when no saved project can be restored', async () => { + await import('../../../src/components/app/pixel-forge-app'); + + const element = document.createElement('pixel-forge-app') as HTMLElement & { + hasLibraryProject: boolean; + showProjectBrowser: boolean; + updateComplete: Promise; + }; + + document.body.append(element); + + await vi.waitFor(() => { + expect(projectRepositoryMock.list).toHaveBeenCalled(); + }); + await element.updateComplete; + + expect(element.hasLibraryProject).toBe(false); + expect(element.showProjectBrowser).toBe(false); + expect(element.shadowRoot?.querySelector('pf-project-browser')).toBeNull(); + expect(element.shadowRoot?.querySelector('pf-drawing-canvas')).toBeTruthy(); + }); + it('opens the project browser from the project tab strip', async () => { await import('../../../src/components/app/pixel-forge-app'); @@ -117,6 +139,9 @@ describe('pixel-forge-app project dialogs', () => { }; document.body.append(element); + await vi.waitFor(() => { + expect(projectRepositoryMock.list).toHaveBeenCalled(); + }); await element.updateComplete; element.showProjectBrowser = false; @@ -129,7 +154,52 @@ describe('pixel-forge-app project dialogs', () => { await element.updateComplete; expect(element.showProjectBrowser).toBe(true); - expect(element.shadowRoot?.querySelector('pf-project-browser')).toBeTruthy(); + const browser = element.shadowRoot?.querySelector('pf-project-browser'); + expect(browser).toBeTruthy(); + expect(browser?.canClose).toBe(true); + + browser?.dispatchEvent( + new CustomEvent('project-browser-close', { bubbles: true, composed: true }) + ); + await element.updateComplete; + + expect(element.showProjectBrowser).toBe(false); + expect(element.shadowRoot?.querySelector('pf-project-browser')).toBeNull(); + }); + + it('requires another project after deleting the active library project', async () => { + await import('../../../src/components/app/pixel-forge-app'); + + const element = document.createElement('pixel-forge-app') as HTMLElement & { + hasLibraryProject: boolean; + showProjectBrowser: boolean; + updateComplete: Promise; + }; + + document.body.append(element); + await element.updateComplete; + + element.hasLibraryProject = true; + element.showProjectBrowser = true; + await element.updateComplete; + + const browser = element.shadowRoot?.querySelector('pf-project-browser'); + browser?.dispatchEvent( + new CustomEvent('current-project-deleted', { bubbles: true, composed: true }) + ); + await element.updateComplete; + + const requiredBrowser = element.shadowRoot?.querySelector('pf-project-browser'); + expect(element.hasLibraryProject).toBe(false); + expect(element.showProjectBrowser).toBe(true); + expect(requiredBrowser?.canClose).toBe(false); + + requiredBrowser?.dispatchEvent( + new CustomEvent('project-browser-close', { bubbles: true, composed: true }) + ); + await element.updateComplete; + + expect(element.showProjectBrowser).toBe(true); }); it('renders the canvas surface from the active project context', async () => { @@ -183,6 +253,14 @@ describe('pixel-forge-app project dialogs', () => { expect(element.showNewProjectDialog).toBe(true); expect(element.shadowRoot?.querySelector('pf-project-browser')).toBeNull(); expect(element.shadowRoot?.querySelector('pf-new-project-dialog')?.open).toBe(true); + + element.shadowRoot?.querySelector('pf-new-project-dialog')?.dispatchEvent( + new CustomEvent('close', { bubbles: true, composed: true }) + ); + await element.updateComplete; + + expect(element.showNewProjectDialog).toBe(false); + expect(element.showProjectBrowser).toBe(false); }); it('opens guided setup when the project browser requests it', async () => { @@ -198,7 +276,7 @@ describe('pixel-forge-app project dialogs', () => { document.body.append(element); await element.updateComplete; - element.hasLibraryProject = true; + element.hasLibraryProject = false; element.showProjectBrowser = true; element.showPaintByNumberDialog = false; await element.updateComplete; @@ -213,5 +291,13 @@ describe('pixel-forge-app project dialogs', () => { expect(element.showPaintByNumberDialog).toBe(true); expect(element.shadowRoot?.querySelector('pf-project-browser')).toBeNull(); expect(element.shadowRoot?.querySelector('pf-paint-by-number-dialog')?.open).toBe(true); + + element.shadowRoot?.querySelector('pf-paint-by-number-dialog')?.dispatchEvent( + new CustomEvent('close', { bubbles: true, composed: true }) + ); + await element.updateComplete; + + expect(element.showPaintByNumberDialog).toBe(false); + expect(element.showProjectBrowser).toBe(false); }); });