Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 103 additions & 51 deletions apps/desktop/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ function succeededResult() {
id: "verse-1",
label: "Verse 1",
groove: "Straight eighths with a late snare feel",
timeRange: { start: 10, end: 30 },
confidence: {
level: "medium",
source: "model",
Expand Down Expand Up @@ -75,6 +76,7 @@ function succeededResult() {
rehearsalPriority: "medium",
simplification: "Keep the sustained note centered; skip the ad-lib on the first pass.",
setupNote: "Watch the breath before the last line of the verse.",
overlapWarnings: [{ targetRoleId: "keys-right", severity: "high", description: "Clash" }],
manualOverrides: [
{
field: "harmony",
Expand Down Expand Up @@ -199,6 +201,19 @@ describe("App", () => {

it("starts an analysis job and renders the returned rehearsal result", async () => {
tauriInvoke
.mockResolvedValueOnce({
projectId: "project-1",
sourceMode: "reference",
projectRoot: "/tmp/bandscope/projects/project-1",
cacheRoot: "/tmp/bandscope/cache/project-1",
tempRoot: "/tmp/bandscope/temp/project-1",
source: {
sourcePath: "/Users/test/Music/late-night-set.wav",
fileName: "late-night-set.wav",
extension: "wav",
fileSizeBytes: 1024000
}
})
.mockResolvedValueOnce({
jobId: "job-1",
state: "queued",
Expand All @@ -209,6 +224,11 @@ describe("App", () => {
.mockResolvedValueOnce(succeededResult());

render(<App />);

fireEvent.click(screen.getByRole("button", { name: /choose local audio/i }));
await waitFor(() => {
expect(screen.getByText(/late-night-set\.wav/i)).toBeTruthy();
});

fireEvent.click(screen.getByRole("button", { name: /start analysis/i }));

Expand All @@ -219,41 +239,39 @@ describe("App", () => {
expect(screen.getByRole("heading", { name: /Late Night Set/i })).toBeTruthy();
});

expect(screen.getByText(/Bass Guitar/i)).toBeTruthy();
expect(tauriInvoke).toHaveBeenNthCalledWith(1, "start_analysis_job", {
expect(screen.getAllByText(/Bass Guitar/i).length).toBeGreaterThan(0);
expect(tauriInvoke).toHaveBeenNthCalledWith(2, "start_analysis_job", {
request: {
sourceKind: "demo",
sourceLabel: "Late Night Set",
sourceKind: "local_audio",
projectId: "project-1",
sourceLabel: "late-night-set.wav",
roleFocus: ["bass-guitar", "keys-right", "lead-vocal"]
}
});
expect(tauriInvoke).toHaveBeenNthCalledWith(2, "get_analysis_job_status", {
jobId: "job-1"
});
});

it("shows a safe failed status when the job poll returns an error", async () => {
tauriInvoke
.mockResolvedValueOnce({
projectId: "project-1",
sourceMode: "reference",
source: { fileName: "late-night-set.wav" }
})
.mockResolvedValueOnce({
jobId: "job-2",
state: "running",
requestedAt: "2026-03-12T00:00:00.000Z",
updatedAt: "2026-03-12T00:00:00.000Z",
progressLabel: "Running analysis"
state: "running"
})
.mockResolvedValueOnce({
jobId: "job-2",
state: "failed",
requestedAt: "2026-03-12T00:00:00.000Z",
updatedAt: "2026-03-12T00:00:01.000Z",
error: {
code: "engine_unavailable",
message: "Analysis engine is unavailable."
}
error: { message: "Analysis engine is unavailable." }
});

render(<App />);

fireEvent.click(screen.getByRole("button", { name: /choose local audio/i }));
await waitFor(() => expect(screen.getByText(/late-night-set\.wav/i)).toBeTruthy());

fireEvent.click(screen.getByRole("button", { name: /start analysis/i }));

await waitFor(() => {
Expand All @@ -263,46 +281,51 @@ describe("App", () => {

it("falls back to a generic failure message when the engine omits details", async () => {
tauriInvoke
.mockResolvedValueOnce({
projectId: "project-1",
sourceMode: "reference",
source: { fileName: "late-night-set.wav" }
})
.mockResolvedValueOnce({
jobId: "job-3",
state: "running",
requestedAt: "2026-03-12T00:00:00.000Z",
updatedAt: "2026-03-12T00:00:00.000Z",
progressLabel: "Running analysis"
state: "running"
})
.mockResolvedValueOnce({
jobId: "job-3",
state: "failed",
requestedAt: "2026-03-12T00:00:00.000Z",
updatedAt: "2026-03-12T00:00:01.000Z",
error: {
code: "engine_unavailable"
}
error: { code: "engine_unavailable" }
});

render(<App />);

fireEvent.click(screen.getByRole("button", { name: /choose local audio/i }));
await waitFor(() => expect(screen.getByText(/late-night-set\.wav/i)).toBeTruthy());

fireEvent.click(screen.getByRole("button", { name: /start analysis/i }));

await waitFor(() => {
expect(screen.getByText(/analysis could not start/i)).toBeTruthy();
expect(screen.getByText(/analysis failed during execution/i)).toBeTruthy();
});
});

it("shows a generic failure when polling rejects", async () => {
tauriInvoke
.mockResolvedValueOnce({
projectId: "project-1",
sourceMode: "reference",
source: { fileName: "late-night-set.wav" }
})
.mockResolvedValueOnce({
jobId: "job-4",
state: "running",
requestedAt: "2026-03-12T00:00:00.000Z",
updatedAt: "2026-03-12T00:00:00.000Z",
progressLabel: "Running analysis"
state: "running"
})
.mockRejectedValueOnce(new Error("transport down"));

render(<App />);

fireEvent.click(screen.getByRole("button", { name: /choose local audio/i }));
await waitFor(() => expect(screen.getByText(/late-night-set\.wav/i)).toBeTruthy());

fireEvent.click(screen.getByRole("button", { name: /start analysis/i }));

await waitFor(() => {
Expand All @@ -311,10 +334,19 @@ describe("App", () => {
});

it("shows a generic failure when starting the job rejects", async () => {
tauriInvoke.mockRejectedValueOnce(new Error("invoke failed"));
tauriInvoke
.mockResolvedValueOnce({
projectId: "project-1",
sourceMode: "reference",
source: { fileName: "late-night-set.wav" }
})
.mockRejectedValueOnce(new Error("invoke failed"));

render(<App />);

fireEvent.click(screen.getByRole("button", { name: /choose local audio/i }));
await waitFor(() => expect(screen.getByText(/late-night-set\.wav/i)).toBeTruthy());

fireEvent.click(screen.getByRole("button", { name: /start analysis/i }));

await waitFor(() => {
Expand All @@ -323,19 +355,23 @@ describe("App", () => {
});

it("shows the direct failure message when start returns a failed job", async () => {
tauriInvoke.mockResolvedValueOnce({
jobId: "job-5",
state: "failed",
requestedAt: "2026-03-12T00:00:00.000Z",
updatedAt: "2026-03-12T00:00:00.000Z",
error: {
code: "engine_unavailable",
message: "Analysis queue is full. Please wait for a running job to finish."
}
});
tauriInvoke
.mockResolvedValueOnce({
projectId: "project-1",
sourceMode: "reference",
source: { fileName: "late-night-set.wav" }
})
.mockResolvedValueOnce({
jobId: "job-5",
state: "failed",
error: { message: "Analysis queue is full. Please wait for a running job to finish." }
});

render(<App />);

fireEvent.click(screen.getByRole("button", { name: /choose local audio/i }));
await waitFor(() => expect(screen.getByText(/late-night-set\.wav/i)).toBeTruthy());

fireEvent.click(screen.getByRole("button", { name: /start analysis/i }));

await waitFor(() => {
Expand All @@ -344,15 +380,22 @@ describe("App", () => {
});

it("falls back to generic text when start returns a failed job without details", async () => {
tauriInvoke.mockResolvedValueOnce({
jobId: "job-6",
state: "failed",
requestedAt: "2026-03-12T00:00:00.000Z",
updatedAt: "2026-03-12T00:00:00.000Z"
});
tauriInvoke
.mockResolvedValueOnce({
projectId: "project-1",
sourceMode: "reference",
source: { fileName: "late-night-set.wav" }
})
.mockResolvedValueOnce({
jobId: "job-6",
state: "failed"
});

render(<App />);

fireEvent.click(screen.getByRole("button", { name: /choose local audio/i }));
await waitFor(() => expect(screen.getByText(/late-night-set\.wav/i)).toBeTruthy());

fireEvent.click(screen.getByRole("button", { name: /start analysis/i }));

await waitFor(() => {
Expand All @@ -361,15 +404,24 @@ describe("App", () => {
});

it("renders the result immediately when start returns a succeeded job", async () => {
tauriInvoke.mockResolvedValueOnce(succeededResult());
tauriInvoke
.mockResolvedValueOnce({
projectId: "project-1",
sourceMode: "reference",
source: { fileName: "late-night-set.wav" }
})
.mockResolvedValueOnce(succeededResult());

render(<App />);

fireEvent.click(screen.getByRole("button", { name: /choose local audio/i }));
await waitFor(() => expect(screen.getByText(/late-night-set\.wav/i)).toBeTruthy());

fireEvent.click(screen.getByRole("button", { name: /start analysis/i }));

await waitFor(() => {
expect(screen.getByText(/manual override: C#m11 \(User-confirmed\)/i)).toBeTruthy();
expect(screen.getByText(/Section Roadmap/i)).toBeTruthy();
});
expect(tauriInvoke).toHaveBeenCalledTimes(1);
expect(tauriInvoke).toHaveBeenCalledTimes(2); // select + start
});
});
Loading
Loading