{{ note }}
} +- Bold p-values are below 0.05. Each interval is drawn on its own scale around zero, so - compare direction and whether it crosses zero, not size between rows. -
-Primary view for comparing factors. Tables below list the same estimates with exact values.
+{{ note }}
+ }+ Bold p-values are below 0.05. + @if (showsInterval()) { + Each interval is drawn on its own scale around + {{ model.nullValue === 1 ? ratioPhrase() + ' of 1 (log scale)' : 'zero' }}, so compare direction and whether it + crosses {{ model.nullValue }}, not size between rows. + } + @if (hasLevels()) { + A category row compares that level with the variable's reference level, the one not listed. + } +
+No results available for this experiment yet.
} diff --git a/src/app/pages/experiments-dashboard/experiment-detail/experiment-detail.component.spec.ts b/src/app/pages/experiments-dashboard/experiment-detail/experiment-detail.component.spec.ts new file mode 100644 index 00000000..c145ce85 --- /dev/null +++ b/src/app/pages/experiments-dashboard/experiment-detail/experiment-detail.component.spec.ts @@ -0,0 +1,68 @@ +import { provideZonelessChangeDetection, signal } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { provideRouter } from '@angular/router'; +import { provideEchartsCore } from 'ngx-echarts'; +import { of } from 'rxjs'; + +import { Experiment } from '../../../models/experiments-dashboard.model'; +import { ExperimentLabelService } from '../../../services/experiment-label.service'; +import { ExperimentsDashboardService } from '../../../services/experiments-dashboard.service'; +import { ExperimentStudioService } from '../../../services/experiment-studio.service'; +import { ExperimentDetailsComponent } from './experiment-detail.component'; + +describe('ExperimentDetailsComponent', () => { + const experiment: Experiment = { + id: 'run-1', + name: 'Age by sex', + dateCreated: new Date('2026-01-01T00:00:00.000Z'), + status: 'success', + algorithmName: 'ttest_independent', + author: 'Marie Curie', + authorEmail: 'marie.curie@chuv.ch', + isShared: false, + }; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ExperimentDetailsComponent], + providers: [ + provideZonelessChangeDetection(), + provideRouter([]), + provideEchartsCore({ echarts: () => import('echarts') }), + { + provide: ExperimentsDashboardService, + useValue: { + getExperimentResult: () => of({ + result: { t_stat: -3.9, df: 120, p: 0.0001, mean_diff: -1.2, ci_lower: -1.8, ci_upper: -0.6 }, + analysis: { algorithm: { x: ['sex'], y: ['age'], parameters: { groupA: 'M', groupB: 'F' } } }, + }), + }, + }, + { provide: ExperimentLabelService, useValue: { getLabelMap: async () => ({}), getEnumMaps: async () => ({}) } }, + { + provide: ExperimentStudioService, + useValue: { + backendAlgorithms: signal({}), + algorithmAssignableVariables: () => [], + selectedFilters: () => [], + getDatasetLabelMap: () => ({}), + formatPreprocessingConfig: () => '', + formatPreprocessingEntries: () => [], + }, + }, + ], + }).compileComponents(); + }); + + it('hands the run parameters to the result, which names the groups the t-test compared', async () => { + const fixture = TestBed.createComponent(ExperimentDetailsComponent); + fixture.componentRef.setInput('selectedExperiment', experiment); + fixture.detectChanges(); + await fixture.whenStable(); + fixture.detectChanges(); + + expect(fixture.componentInstance.runParameters()).toEqual({ groupA: 'M', groupB: 'F' }); + const figures = (fixture.nativeElement as HTMLElement).querySelector('app-key-figures'); + expect(figures?.textContent).toContain('M − F'); + }); +}); diff --git a/src/app/pages/experiments-dashboard/experiment-detail/experiment-detail.component.ts b/src/app/pages/experiments-dashboard/experiment-detail/experiment-detail.component.ts index 6da1d5c0..8f07e028 100644 --- a/src/app/pages/experiments-dashboard/experiment-detail/experiment-detail.component.ts +++ b/src/app/pages/experiments-dashboard/experiment-detail/experiment-detail.component.ts @@ -286,6 +286,8 @@ export class ExperimentDetailsComponent { ); } + readonly runParameters = computed(() => this.fullExperimentSignal()?.analysis?.algorithm?.parameters ?? null); + readonly hasStoredParameters = computed(() => Object.values(this.fullExperimentSignal()?.analysis?.algorithm?.parameters ?? {}) .some((value) => !this.isEmptyParameterValue(value)) diff --git a/src/app/pages/experiments-dashboard/experiments-compare/experiments-compare.component.html b/src/app/pages/experiments-dashboard/experiments-compare/experiments-compare.component.html index 4feb70dd..82b1e6cd 100644 --- a/src/app/pages/experiments-dashboard/experiments-compare/experiments-compare.component.html +++ b/src/app/pages/experiments-dashboard/experiments-compare/experiments-compare.component.html @@ -124,7 +124,8 @@