Skip to content

Commit f12d316

Browse files
committed
test: add ecg print workflow acceptance
1 parent 011a30d commit f12d316

2 files changed

Lines changed: 76 additions & 2 deletions

File tree

.agents/migration_guide.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ Current facts:
135135
- `GuiLayoutRhsPreviewTest` adds the first RHS/large-file representative
136136
workflow test, covering synthetic RHS indexing, automatic preview drawing,
137137
preview channel table population, and filter-file discovery.
138+
- `GuiLayoutEcgPrintTest` covers wearable ECG workflow: synthetic CSV recording
139+
load, channel discovery, ROI analysis, summary-table refresh, and waveform,
140+
noise, SNR, and template plot redraw.
138141
- `GuiLayoutBatchCropTest` covers a second image workflow shape: synthetic
139142
image load, center confirmation, default output-folder export, manifest
140143
creation, and crop-file creation.
@@ -341,8 +344,8 @@ Migration workstreams:
341344

342345
1. Extend workflow acceptance beyond the current representatives
343346
(`eis`, `chrono_overlay`, `vt_resistance`, `cic`, `csc`, `focus_stack`,
344-
`batch_crop`, `image_enhance`, `image_match`, `rhs_preview`) to the remaining supported app
345-
families. Start with CI-sized synthetic inputs, then record which app
347+
`batch_crop`, `image_enhance`, `image_match`, `rhs_preview`, `ecg_print`)
348+
to the remaining supported app families. Start with CI-sized synthetic inputs, then record which app
346349
families merit larger manual or scheduled stress cases.
347350
2. Extend `tests/shared/labkitWorkflowDriver.m` only for app-neutral semantic
348351
operations proven by real workflow tests. Avoid vague helpers that guess app

tests/cases/gui/apps/wearable/ecg_print/GuiLayoutEcgPrintTest.m

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,75 @@ function ecg_print_layout(testCase)
3030
h.axesSpec('Template + Residual Band', 'Time from peak (s)', 'Amplitude')});
3131
end
3232
end
33+
34+
methods (Test, TestTags = {'GUI', 'Workflow'})
35+
function ecg_print_workflow_loads_analyzes_and_plots_recording(testCase)
36+
setupLabKitTestPath();
37+
h = guiTestHelpers();
38+
h.assertUifigureAvailable();
39+
cleanup = onCleanup(@() h.closeAllFigures());
40+
41+
folder = tempname;
42+
mkdir(folder);
43+
folderCleanup = onCleanup(@() removeTempFolder(folder));
44+
recordingPath = fullfile(folder, 'synthetic_ecg.csv');
45+
writeSyntheticEcgCsv(recordingPath);
46+
47+
fig = h.launchFigure('labkit_ECGPrint_app', ...
48+
'ECG Signal Print + SNR Explorer');
49+
driver = labkitWorkflowDriver(fig);
50+
driver.chooseFiles('recording', recordingPath);
51+
52+
driver.click('Open recording');
53+
driver.dropdown('Local peaks');
54+
driver.click('Analyze current ROI');
55+
56+
ui = driver.registry();
57+
testCase.verifyTrue(contains(string(ui.controls.recording.status.Value), ...
58+
'synthetic_ecg.csv'), ...
59+
'ECG Print workflow should show the loaded recording path.');
60+
importStatus = string(ui.controls.importStatus.valueHandle.Value);
61+
testCase.verifyFalse(contains(importStatus, 'Parse failed'), ...
62+
'ECG Print workflow should parse the synthetic recording.');
63+
testCase.verifyFalse(contains(importStatus, 'Open a recording'), ...
64+
'ECG Print workflow should leave the empty import state after loading.');
65+
testCase.verifyTrue(any(strcmp(ui.controls.channel.valueHandle.Items, 'ECG')), ...
66+
'ECG Print workflow should populate the ECG channel choice.');
67+
68+
data = driver.tableData('summaryTable');
69+
metricNames = string(data(:, 1));
70+
testCase.verifyTrue(any(metricNames == "Detected peaks"), ...
71+
'ECG Print workflow should report detected peaks after analysis.');
72+
testCase.verifyTrue(any(metricNames == "Mean SNR (dB)"), ...
73+
'ECG Print workflow should report SNR summary after analysis.');
74+
testCase.verifyGreaterThan(numel(ui.controls.previewAxes.axesById.wave.Children), 0, ...
75+
'ECG Print workflow should draw the waveform plot.');
76+
testCase.verifyGreaterThan(numel(ui.controls.previewAxes.axesById.noise.Children), 0, ...
77+
'ECG Print workflow should draw the noise plot.');
78+
testCase.verifyGreaterThan(numel(ui.controls.previewAxes.axesById.snr.Children), 0, ...
79+
'ECG Print workflow should draw the SNR plot.');
80+
testCase.verifyGreaterThan(numel(ui.controls.previewAxes.axesById.template.Children), 0, ...
81+
'ECG Print workflow should draw the template plot.');
82+
end
83+
end
84+
end
85+
86+
function writeSyntheticEcgCsv(filepath)
87+
fs = 2000;
88+
durationSec = 8;
89+
time = (0:(durationSec * fs - 1)).' ./ fs;
90+
ecg = 0.05 .* sin(2 .* pi .* 1.7 .* time) + ...
91+
0.02 .* sin(2 .* pi .* 23 .* time);
92+
for peakTime = 1:durationSec - 1
93+
ecg = ecg + 1.8 .* exp(-((time - peakTime) ./ 0.018).^2);
94+
ecg = ecg - 0.25 .* exp(-((time - peakTime - 0.045) ./ 0.026).^2);
95+
end
96+
T = table(time, ecg, 'VariableNames', {'time_s', 'ECG'});
97+
writetable(T, filepath);
98+
end
99+
100+
function removeTempFolder(folder)
101+
if exist(folder, 'dir') == 7
102+
rmdir(folder, 's');
103+
end
33104
end

0 commit comments

Comments
 (0)