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
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,40 @@ class FakeMediaRecorder {
}
}

class FakeAudioContext {
state: AudioContextState = "running";
sampleRate = 48_000;

createAnalyser() {
return {
fftSize: 256,
frequencyBinCount: 128,
smoothingTimeConstant: 0,
getByteTimeDomainData(samples: Uint8Array) {
samples.fill(128);
},
getByteFrequencyData(samples: Uint8Array) {
samples.fill(0);
},
} as unknown as AnalyserNode;
}

createMediaStreamSource() {
return {
connect() {},
disconnect() {},
} as unknown as MediaStreamAudioSourceNode;
}

resume() {
return Promise.resolve();
}

close() {
return Promise.resolve();
}
}

const getUserMedia = vi.fn();

function installBrowserRecordingAPIs() {
Expand All @@ -203,6 +237,11 @@ function installBrowserRecordingAPIs() {
writable: true,
value: FakeMediaRecorder,
});
Object.defineProperty(globalThis, "AudioContext", {
configurable: true,
writable: true,
value: FakeAudioContext,
});
Object.defineProperty(navigator, "mediaDevices", {
configurable: true,
writable: true,
Expand Down Expand Up @@ -308,12 +347,41 @@ describe("onboarding brain dump — flag gating", () => {
render(<OnboardingPage />);

expect(await screen.findByText(DUMP_HEADLINE)).toBeDefined();
expect(
screen.getByRole("button", { name: "Start recording" }),
).toBeDefined();
expect(screen.getByRole("button", { name: "Start talking" })).toBeDefined();
expect(screen.queryByTestId("orb-progress-ring")).toBeNull();
expect(screen.queryByText(PILLBOX_HEADING)).toBeNull();
});

it("renders the original orb with reactive audio bars", async () => {
mockFlags = { "onboarding-brain-dump": true };
landOnPainPointsStep();

render(<OnboardingPage />);

expect(await screen.findByTestId("orb-current")).toBeDefined();
expect(screen.getByTestId("orb-frame").style.width).toBe("184px");
expect(screen.getByTestId("orb-decorative-ring")).toBeDefined();
expect(screen.getByTestId("orb-audio-bars")).toBeDefined();
const audioBars = screen.getAllByTestId("orb-audio-bar");
expect(audioBars).toHaveLength(5);
expect(audioBars.map((bar) => bar.style.height)).toEqual([
"22px",
"34px",
"46px",
"34px",
"22px",
]);
expect(audioBars.map((bar) => bar.style.transform)).toEqual([
"scaleY(0.48)",
"scaleY(0.58)",
"scaleY(0.72)",
"scaleY(0.58)",
"scaleY(0.48)",
]);
expect(screen.queryByRole("combobox", { name: "Orb style" })).toBeNull();
expect(screen.getByRole("button", { name: "Start talking" })).toBeDefined();
});

it("leaves the pillboxes untouched and makes no brain-dump request when the flag is off", async () => {
const calls = recordBrainDumpTraffic();
mockFlags = {};
Expand All @@ -326,9 +394,7 @@ describe("onboarding brain dump — flag gating", () => {
screen.getByText("Pick the tasks you'd love to hand off to AutoPilot"),
).toBeDefined();
expect(screen.queryByText(DUMP_HEADLINE)).toBeNull();
expect(
screen.queryByRole("button", { name: "Start recording" }),
).toBeNull();
expect(screen.queryByRole("button", { name: "Start talking" })).toBeNull();
expect(screen.queryByText("Skip for now")).toBeNull();

// Give any stray effect a chance to fire before declaring silence.
Expand Down Expand Up @@ -366,7 +432,7 @@ describe("onboarding brain dump — typed fallback", () => {
await screen.findByText(DUMP_HEADLINE);

await userEvent.click(
screen.getByRole("button", { name: "Start recording" }),
screen.getByRole("button", { name: "Start talking" }),
);

expect(
Expand All @@ -376,9 +442,7 @@ describe("onboarding brain dump — typed fallback", () => {
).toBeDefined();
// Same headline, not a dead end.
expect(screen.getByText(DUMP_HEADLINE)).toBeDefined();
expect(
screen.queryByRole("button", { name: "Start recording" }),
).toBeNull();
expect(screen.queryByRole("button", { name: "Start talking" })).toBeNull();
// Offering a way back to the orb would be a dead end here: the browser
// has already refused the microphone.
expect(screen.queryByRole("button", { name: "record instead" })).toBeNull();
Expand Down Expand Up @@ -454,13 +518,13 @@ describe("onboarding brain dump — finishing a take", () => {
await screen.findByText(DUMP_HEADLINE);

await userEvent.click(
screen.getByRole("button", { name: "Start recording" }),
screen.getByRole("button", { name: "Start talking" }),
);
expect(await screen.findByTestId("orb-progress-ring")).toBeDefined();
await waitFor(() => expect(partUploads).toHaveLength(1));
const doneButtons = await screen.findAllByRole("button", {
name: "I'm done",
});
await userEvent.click(doneButtons[doneButtons.length - 1]);
await userEvent.click(
await screen.findByRole("button", { name: "Send recording" }),
);

expect(await screen.findByTestId("step-preparing")).toBeDefined();
expect(bodies).toHaveLength(1);
Expand Down Expand Up @@ -501,17 +565,101 @@ describe("onboarding brain dump — finishing a take", () => {
expect(screen.getByRole("button", { name: "Skip for now" })).toBeDefined();

await userEvent.click(
screen.getByRole("button", { name: "Start recording" }),
screen.getByRole("button", { name: "Start talking" }),
);
await waitFor(() => expect(partUploads).toHaveLength(1));
const doneButtons = await screen.findAllByRole("button", {
name: "I'm done",
});
await userEvent.click(doneButtons[doneButtons.length - 1]);
await userEvent.click(
await screen.findByRole("button", { name: "Send recording" }),
);

expect(await screen.findByText("Got it. One second…")).toBeDefined();
expect(screen.queryByRole("button", { name: "Skip for now" })).toBeNull();
});

it("shows immediate progress while canceling a recording", async () => {
let finishDiscard: (() => void) | undefined;
recordBrainDumpTraffic();
server.use(
getDiscardBrainDumpMockHandler200(async () => {
await new Promise<void>((resolve) => {
finishDiscard = resolve;
});
return { status: null };
}),
);
mockFlags = { "onboarding-brain-dump": true };
landOnPainPointsStep();

render(<OnboardingPage />);
await screen.findByText(DUMP_HEADLINE);
await userEvent.click(
screen.getByRole("button", { name: "Start talking" }),
);
await userEvent.click(
await screen.findByRole("button", { name: "Cancel recording" }),
);

expect(await screen.findByText("Discard recording?")).toBeDefined();
expect(
screen.getByText(/This permanently deletes your current take/),
).toBeDefined();

await userEvent.click(
screen.getByRole("button", { name: "Keep recording" }),
);
expect(screen.queryByText("Discard recording?")).toBeNull();
expect(finishDiscard).toBeUndefined();

await userEvent.click(
screen.getByRole("button", { name: "Cancel recording" }),
);
await userEvent.click(
await screen.findByRole("button", { name: "Discard recording" }),
);

expect(screen.getByTestId("recording-feedback-slot")).toBeDefined();
const cancelingButton = (await screen.findByRole("button", {
name: "Canceling recording",
})) as HTMLButtonElement;
expect(cancelingButton.getAttribute("aria-busy")).toBe("true");
expect(await screen.findByTestId("recording-control-loader")).toBeDefined();
expect(await screen.findByText("Discarding this take…")).toBeDefined();
expect(
(
screen.getByRole("button", {
name: "Send recording",
}) as HTMLButtonElement
).disabled,
).toBe(false);
expect(
screen
.getByRole("button", { name: "Send recording" })
.getAttribute("aria-disabled"),
).toBe("true");
expect(
(
screen.getByRole("button", {
name: "Retry recording",
}) as HTMLButtonElement
).disabled,
).toBe(false);
expect(
screen
.getByRole("button", { name: "Retry recording" })
.getAttribute("aria-disabled"),
).toBe("true");
expect(cancelingButton.disabled).toBe(false);
expect(cancelingButton.getAttribute("aria-disabled")).toBe("true");
await waitFor(() => expect(document.activeElement).toBe(cancelingButton));
await userEvent.click(cancelingButton);
expect(screen.queryByText("Discard recording?")).toBeNull();

await waitFor(() => expect(finishDiscard).toBeDefined());
finishDiscard!();
expect(
await screen.findByRole("button", { name: "Start talking" }),
).toBeDefined();
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});

describe("onboarding brain dump — recovery", () => {
Expand Down Expand Up @@ -576,9 +724,7 @@ describe("onboarding brain dump — recovery", () => {
await waitFor(() =>
expect(screen.queryByText("Pick up where you left off?")).toBeNull(),
);
expect(
screen.getByRole("button", { name: "Start recording" }),
).toBeDefined();
expect(screen.getByRole("button", { name: "Start talking" })).toBeDefined();
});
});

Expand Down Expand Up @@ -607,16 +753,15 @@ describe("onboarding brain dump — failure", () => {
await screen.findByText(DUMP_HEADLINE);

await userEvent.click(
screen.getByRole("button", { name: "Start recording" }),
screen.getByRole("button", { name: "Start talking" }),
);
// Wait for the first chunk to reach the server so "I'm done" is not
// Wait for the first chunk to reach the server so sending is not
// racing the upload queue.
await waitFor(() => expect(partUploads).toHaveLength(1));

const doneButtons = await screen.findAllByRole("button", {
name: "I'm done",
});
await userEvent.click(doneButtons[doneButtons.length - 1]);
await userEvent.click(
await screen.findByRole("button", { name: "Send recording" }),
);

expect(await screen.findByText("That didn't go through.")).toBeDefined();
// The failure has to come from finalize reporting `failed`, not from the
Expand Down
Loading
Loading