fixed hidden quiet hours fields blocking save

This commit is contained in:
Flaminel committed 2026-10-23 15:52:58 +03:00
1 parent 7a8c052942
commit 68ad519bd9
6 files changed
+82 -7

No files matched your search

@@ -252,6 +252,7 @@ public class SeekerConfigControllerTests : IDisposable
{
SearchEnabled = true,
SearchInterval = 5,
ProactiveSearchEnabled = true,
QuietHoursEnabled = true,
QuietHoursStart = new TimeOnly(7, 0),
QuietHoursEnd = new TimeOnly(7, 0),
@@ -107,6 +107,8 @@ public sealed class SeekerConfigTests
{
SeekerConfig config = new()
{
ProactiveSearchEnabled = true,
QuietHoursEnabled = true,
QuietHoursStart = new TimeOnly(5, 0),
QuietHoursEnd = new TimeOnly(5, 0)
};
@@ -114,6 +116,24 @@ public sealed class SeekerConfigTests
Should.Throw<CustomValidationException>(() => config.Validate());
}
[Theory]
[InlineData(false, true, true)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public void Validate_WithEqualQuietHoursThatNeverApply_DoesNotThrow(bool searchEnabled, bool proactiveSearchEnabled, bool quietHoursEnabled)
{
SeekerConfig config = new()
{
SearchEnabled = searchEnabled,
ProactiveSearchEnabled = proactiveSearchEnabled,
QuietHoursEnabled = quietHoursEnabled,
QuietHoursStart = new TimeOnly(5, 0),
QuietHoursEnd = new TimeOnly(5, 0)
};
Should.NotThrow(() => config.Validate());
}
[Fact]
public void IsQuietTime_WhenDisabled_ReturnsFalse()
{
@@ -90,7 +90,7 @@ public sealed record SeekerConfig : IConfig
throw new CustomValidationException($"{nameof(PostReleaseGraceHours)} must be between 0 and 72");
}
if (QuietHoursStart == QuietHoursEnd)
if (SearchEnabled && ProactiveSearchEnabled && QuietHoursEnabled && QuietHoursStart == QuietHoursEnd)
{
throw new CustomValidationException($"{nameof(QuietHoursStart)} and {nameof(QuietHoursEnd)} must differ");
}
@@ -608,6 +608,53 @@ describe('SeekerComponent', () => {
expect(saveButton(fixture).disabled).toBe(false);
});
it('blocks saving while a visible quiet hours time is cleared', () => {
const { fixture, component } = setup();
component.seekerForm.quietHoursStart().value.set('');
fixture.detectChanges();
expect(component.hasErrors()).toBe(true);
expect(saveButton(fixture).disabled).toBe(true);
});
it('allows saving a cleared quiet hours time once quiet hours are off', () => {
const { fixture, component } = setup();
component.seekerForm.quietHoursStart().value.set('');
component.seekerForm.quietHoursEnabled().value.set(false);
fixture.detectChanges();
expect(component.hasErrors()).toBe(false);
expect(saveButton(fixture).disabled).toBe(false);
});
it('allows saving a cleared quiet hours time once proactive search is off', () => {
const { fixture, component } = setup();
component.seekerForm.quietHoursEnd().value.set('');
component.seekerForm.proactiveSearchEnabled().value.set(false);
fixture.detectChanges();
expect(component.hasErrors()).toBe(false);
expect(saveButton(fixture).disabled).toBe(false);
});
it('sends default quiet hours for cleared times', () => {
const { fixture, component, updateConfig } = setup();
component.seekerForm.quietHoursStart().value.set('');
component.seekerForm.quietHoursEnd().value.set('');
component.seekerForm.quietHoursEnabled().value.set(false);
fixture.detectChanges();
component.save();
expect(updateConfig).toHaveBeenCalledWith(
expect.objectContaining({ quietHoursStart: '23:00', quietHoursEnd: '07:00' }),
);
});
it('confirms before turning round robin off and leaves it on when cancelled', async () => {
const { fixture, component, confirm } = setup();
@@ -144,14 +144,19 @@ export class SeekerComponent implements HasPendingChanges {
readonly seekerForm = form(this.model, (p) => {
min(p.postReleaseGraceHours, 0);
max(p.postReleaseGraceHours, 72);
required(p.quietHoursStart, { message: 'Start time is required' });
required(p.quietHoursEnd, { message: 'End time is required' });
required(p.quietHoursStart, { when: () => this.quietHoursVisible(), message: 'Start time is required' });
required(p.quietHoursEnd, { when: () => this.quietHoursVisible(), message: 'End time is required' });
validate(p.quietHoursEnd, ({ valueOf }) =>
valueOf(p.quietHoursStart) === valueOf(p.quietHoursEnd)
this.quietHoursVisible() && valueOf(p.quietHoursStart) === valueOf(p.quietHoursEnd)
? { kind: 'sameTime', message: 'Start and end must differ' }
: undefined);
});
private quietHoursVisible(): boolean {
const m: SeekerFormModel = this.model();
return m.searchEnabled && m.proactiveSearchEnabled && m.quietHoursEnabled;
}
readonly instances = signal<InstanceState[]>([]);
readonly strategyDescription = computed(() => STRATEGY_DESCRIPTIONS[this.model().selectionStrategy] ?? '');
@@ -283,8 +288,8 @@ export class SeekerComponent implements HasPendingChanges {
useRoundRobin: m.useRoundRobin,
postReleaseGraceHours: m.postReleaseGraceHours ?? 6,
quietHoursEnabled: m.quietHoursEnabled,
quietHoursStart: m.quietHoursStart,
quietHoursEnd: m.quietHoursEnd,
quietHoursStart: m.quietHoursStart || '23:00',
quietHoursEnd: m.quietHoursEnd || '07:00',
instances: this.instances().map(i => ({
arrInstanceId: i.arrInstanceId,
enabled: i.enabled,
+3 -1
View File
@@ -78,7 +78,9 @@ test.describe.serial('Seeker — config', () => {
const current = await (await api.seeker.getConfig()).json();
const res = await api.seeker.updateConfig({
...current,
quietHoursEnabled: false,
searchEnabled: true,
proactiveSearchEnabled: true,
quietHoursEnabled: true,
quietHoursStart: '08:00',
quietHoursEnd: '08:00',
});