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
93 changes: 71 additions & 22 deletions src/renderer/cypress/support/pbtHarness.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ import type { MediaFileD } from '../../src/model';
import { boldDefaultSegParams } from '../../src/components/PassageDetail/carefulSpeech/boldCarefulSpeechSegParams';
import { regionsJsonFromList } from '../../src/components/PassageDetail/carefulSpeech/carefulSpeechBoundary';
import { prettySegment } from '../../src/utils/prettySegment';
import { parseMediaLanguageBcp47 } from '../../src/utils/mediaLanguage';
import { phraseBtBoundaryRegionName } from '../../src/components/PassageDetail/carefulSpeech/matchesGuidedOutputRow';
import PassageDetailPhraseBackTranslate from '../../src/components/PassageDetail/PassageDetailPhraseBackTranslate';

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -97,8 +99,8 @@ export const USER_ID = 'user-1';
export const ORG_ID = 'org-1';
export const STEP_LANGUAGE = 'English|en';
export const STEP_BCP47 = 'en';
/** Named-region bucket PBT stores its boundaries in (phraseBtBoundaryRegionName). */
export const BT_REGION_NAME = `BT:${STEP_BCP47}`;
/** Named-region bucket PBT stores its boundaries in. */
export const BT_REGION_NAME = phraseBtBoundaryRegionName(STEP_BCP47);

/** Fake S3 host the intercepted POST hands back for the audio PUT. */
const FAKE_AUDIO_HOST = 'https://pbt-test.invalid';
Expand Down Expand Up @@ -144,6 +146,8 @@ interface ServerState {
generation: number;
/** Outstanding lagged writes, so a reset can cancel them. */
pendingTimers: ReturnType<typeof setTimeout>[];
/** ids requested through GET /mediafiles/:id/fileurl (in request order). */
fileurlRequestedIds: string[];
}

const serverState: ServerState = {
Expand All @@ -154,6 +158,7 @@ const serverState: ServerState = {
nextRemoteId: 1000,
generation: 0,
pendingTimers: [],
fileurlRequestedIds: [],
};

/** Reset the fake server. Call from beforeEach before installing intercepts. */
Expand All @@ -168,6 +173,7 @@ export function resetPbtServer(options?: {
serverState.pendingTimers = [];
serverState.generation += 1;
serverState.takes = [];
serverState.fileurlRequestedIds = [];
serverState.nextRemoteId = 1000;
serverState.putDelayMs = options?.putDelayMs ?? 0;
serverState.fileurlDelayMs = options?.fileurlDelayMs ?? 0;
Expand All @@ -182,6 +188,11 @@ export function postedTakes(): PostedTake[] {
return serverState.takes;
}

/** mediafile ids requested by MediaRecord load calls (GET /mediafiles/:id/fileurl). */
export function fileurlRequestedIds(): string[] {
return serverState.fileurlRequestedIds;
}

/** Make later uploads fail (used mid-spec for save-failure paths). */
export function failNextUploads(putStatus = 500) {
serverState.failPutWithStatus = putStatus;
Expand Down Expand Up @@ -269,6 +280,10 @@ export function installPbtServer() {

// Existing-take load (useFetchUrlNow → GET mediafiles/<id>/fileurl).
cy.intercept('GET', '**/mediafiles/*/fileurl', (req) => {
const match = /\/mediafiles\/([^/]+)\/fileurl(?:\?|$)/.exec(req.url);
if (match?.[1]) {
serverState.fileurlRequestedIds.push(decodeURIComponent(match[1]));
}
req.reply({
statusCode: 200,
delay: serverState.fileurlDelayMs,
Expand Down Expand Up @@ -378,6 +393,15 @@ export interface MountPbtOptions {
segments?: SegmentSpec[];
/** Indices that already have a saved take when the step opens. */
existingTakes?: number[];
/** Exact seeded takes for multi-language and duplicate-segment cases. */
existingTakeRows?: Array<{
segmentIndex: number;
languagebcp47: string;
remoteId: string;
performedBy?: string;
}>;
/** Step language stamped in Step Settings (`Name|bcp47`). */
stepLanguage?: string;
/** Source audio length (s). Must cover the last segment end. */
durationSec?: number;
/** ms before an uploaded take shows up in rowData (0 = immediate). */
Expand Down Expand Up @@ -419,15 +443,15 @@ function interiorBoundaries(
.filter((t) => t > 0.01 && t < durationSec - 0.01);
}

function segmentsAttribute(segments: SegmentSpec[]): string {
function segmentsAttribute(segments: SegmentSpec[], bcp47: string): string {
const regions: IRegion[] = segments.map((s) => ({
start: s.start,
end: s.end,
label: '',
}));
return JSON.stringify([
{
name: BT_REGION_NAME,
name: phraseBtBoundaryRegionName(bcp47),
regionInfo: regionsJsonFromList(regions, boldDefaultSegParams),
},
]);
Expand All @@ -437,7 +461,8 @@ function takeRecord(
id: string,
remoteId: string,
sourceSegments: string,
performedBy: string | null
performedBy: string | null,
languagebcp47 = STEP_LANGUAGE
): MediaFileD {
return {
type: 'mediafile',
Expand All @@ -450,7 +475,7 @@ function takeRecord(
originalFile: `${id}.ogg`,
audioUrl: `${FAKE_AUDIO_HOST}/audio/${id}.wav`,
sourceSegments,
languagebcp47: STEP_LANGUAGE,
languagebcp47,
performedBy,
dateCreated: new Date(2026, 0, 1).toISOString(),
segments: '[]',
Expand All @@ -467,10 +492,12 @@ function takeRecord(

function seedRecords(memory: Memory, options: MountPbtOptions) {
const segments = options.segments ?? [];
const stepLanguage = options.stepLanguage ?? STEP_LANGUAGE;
const stepBcp47 = parseMediaLanguageBcp47(stepLanguage);
const stepTool = JSON.stringify({
tool: 'phraseBackTranslate',
settings: JSON.stringify({
language: STEP_LANGUAGE,
language: stepLanguage,
artifactTypeId: ARTIFACT_TYPE_ID,
}),
});
Expand Down Expand Up @@ -536,7 +563,7 @@ function seedRecords(memory: Memory, options: MountPbtOptions) {
contentType: 'audio/wav',
originalFile: 'vern.wav',
audioUrl: `${FAKE_AUDIO_HOST}/audio/vern.wav`,
segments: segmentsAttribute(segments),
segments: segmentsAttribute(segments, stepBcp47),
transcription: '',
},
relationships: {
Expand All @@ -546,19 +573,37 @@ function seedRecords(memory: Memory, options: MountPbtOptions) {
},
];

(options.existingTakes ?? []).forEach((idx) => {
const seg = segments[idx];
if (!seg) return;
records.push(
takeRecord(
`mf-take-${idx}`,
String(500 + idx),
JSON.stringify({ start: seg.start, end: seg.end, label: '' }),
'Existing Speaker'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any
);
});
if ((options.existingTakeRows?.length ?? 0) > 0) {
options.existingTakeRows?.forEach((row) => {
const seg = segments[row.segmentIndex];
if (!seg) return;
records.push(
takeRecord(
`mf-take-${row.remoteId}`,
row.remoteId,
JSON.stringify({ start: seg.start, end: seg.end, label: '' }),
row.performedBy ?? 'Existing Speaker',
row.languagebcp47
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any
);
});
} else {
(options.existingTakes ?? []).forEach((idx) => {
const seg = segments[idx];
if (!seg) return;
records.push(
takeRecord(
`mf-take-${idx}`,
String(500 + idx),
JSON.stringify({ start: seg.start, end: seg.end, label: '' }),
'Existing Speaker',
stepLanguage
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any
);
});
}

memory.cache.update((t) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -693,7 +738,11 @@ function PbtHarnessInner({ options, memory, blob }: HarnessProps) {
id,
take.remoteId,
take.sourceSegments,
take.performedBy
take.performedBy,
// The real pull-after-upload returns what was posted; stamping
// every uploaded take English hid whether the step language ever
// reached the upload at all.
take.languagebcp47
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ jest.mock('./carefulSpeech/useGuidedPhraseSegments', () => ({
setPhraseSegString: jest.fn(),
bootstrapped: true,
ensureSegments: jest.fn().mockResolvedValue(true),
resetForMediafile: jest.fn(),
resetForScope: jest.fn(),
resegmentWithParams: jest.fn().mockResolvedValue(false),
resetToDefaultSegments: jest.fn().mockResolvedValue(false),
persistPhraseSegments: jest.fn().mockResolvedValue(undefined),
Expand Down
Loading
Loading