mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
fix(work-context): make isTodayListActive reactive to context changes (#8863)
task.component's `isTodayListActive` computed read the plain mutable `WorkContextService.isTodayList` boolean, which is not a signal producer, so the computed never invalidated and returned its first value for the component's lifetime. Expose `isTodayListSignal` (a toSignal mirror of isTodayList$) on the service and read that from the computed. The plain boolean stays for the synchronous reader in history.component. #8843
This commit is contained in:
parent
8a1105bbb2
commit
24c799ee13
3 changed files with 86 additions and 2 deletions
|
|
@ -234,7 +234,7 @@ export class TaskComponent implements OnDestroy, AfterViewInit {
|
|||
return 'chat';
|
||||
});
|
||||
|
||||
isTodayListActive = computed(() => this.workContextService.isTodayList);
|
||||
isTodayListActive = computed(() => this.workContextService.isTodayListSignal());
|
||||
taskIdWithPrefix = computed(() => 't-' + this.task().id);
|
||||
isRepeatTaskCreatedToday = computed(
|
||||
() => !!(this.task().repeatCfgId && this._dateService.isToday(this.task().created)),
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ import { TimeTrackingService } from '../time-tracking/time-tracking.service';
|
|||
import { TaskArchiveService } from '../archive/task-archive.service';
|
||||
import { TODAY_TAG } from '../tag/tag.const';
|
||||
import { WorkContext, WorkContextType } from './work-context.model';
|
||||
import { selectActiveWorkContext } from './store/work-context.selectors';
|
||||
import {
|
||||
selectActiveContextId,
|
||||
selectActiveWorkContext,
|
||||
} from './store/work-context.selectors';
|
||||
import { allDataWasLoaded } from '../../root-store/meta/all-data-was-loaded.actions';
|
||||
|
||||
describe('WorkContextService - undoneTasks$ filtering', () => {
|
||||
|
|
@ -642,3 +645,79 @@ describe('WorkContextService - activeWorkContext$ distinctUntilChanged', () => {
|
|||
sub.unsubscribe();
|
||||
});
|
||||
});
|
||||
|
||||
// #8843: `task.component.ts` read `workContextService.isTodayList` (a plain
|
||||
// mutable boolean) inside a `computed()`, so the computed had no signal producer
|
||||
// and never invalidated. `isTodayListSignal` is a `toSignal(isTodayList$)` mirror
|
||||
// that must reactively track the active work context.
|
||||
describe('WorkContextService - isTodayListSignal reactivity', () => {
|
||||
let store: MockStore;
|
||||
let service: WorkContextService;
|
||||
|
||||
beforeEach(() => {
|
||||
const tagServiceMock = jasmine.createSpyObj('TagService', ['getTagById$']);
|
||||
tagServiceMock.getTagById$.and.returnValue(of(TODAY_TAG));
|
||||
|
||||
const globalTrackingIntervalServiceMock = jasmine.createSpyObj(
|
||||
'GlobalTrackingIntervalService',
|
||||
[],
|
||||
{ todayDateStr$: of('2026-04-06') },
|
||||
);
|
||||
|
||||
const dateServiceMock = jasmine.createSpyObj('DateService', ['todayStr']);
|
||||
dateServiceMock.todayStr.and.returnValue('2026-04-06');
|
||||
|
||||
const timeTrackingServiceMock = jasmine.createSpyObj('TimeTrackingService', [
|
||||
'getWorkStartEndForWorkContext$',
|
||||
]);
|
||||
timeTrackingServiceMock.getWorkStartEndForWorkContext$.and.returnValue(of({}));
|
||||
|
||||
const taskArchiveServiceMock = jasmine.createSpyObj('TaskArchiveService', [
|
||||
'loadYoung',
|
||||
]);
|
||||
taskArchiveServiceMock.loadYoung.and.returnValue(
|
||||
Promise.resolve({ ids: [], entities: {} }),
|
||||
);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot()],
|
||||
providers: [
|
||||
provideMockStore({
|
||||
initialState: {
|
||||
workContext: { activeId: TODAY_TAG.id, activeType: 'TAG' },
|
||||
tag: { entities: {}, ids: [] },
|
||||
project: { entities: {}, ids: [] },
|
||||
task: { entities: {}, ids: [] },
|
||||
},
|
||||
}),
|
||||
// activeWorkContextId$ is gated behind the allDataWasLoaded action.
|
||||
provideMockActions(() => of(allDataWasLoaded())),
|
||||
{ provide: Router, useValue: { events: of(), url: '/' } },
|
||||
{ provide: TagService, useValue: tagServiceMock },
|
||||
{
|
||||
provide: GlobalTrackingIntervalService,
|
||||
useValue: globalTrackingIntervalServiceMock,
|
||||
},
|
||||
{ provide: DateService, useValue: dateServiceMock },
|
||||
{ provide: TimeTrackingService, useValue: timeTrackingServiceMock },
|
||||
{ provide: TaskArchiveService, useValue: taskArchiveServiceMock },
|
||||
WorkContextService,
|
||||
],
|
||||
});
|
||||
|
||||
store = TestBed.inject(MockStore);
|
||||
store.overrideSelector(selectActiveContextId, TODAY_TAG.id);
|
||||
store.refreshState();
|
||||
|
||||
service = TestBed.inject(WorkContextService);
|
||||
});
|
||||
|
||||
it('is true on the Today list and flips to false when the context changes', () => {
|
||||
expect(service.isTodayListSignal()).toBe(true);
|
||||
|
||||
store.overrideSelector(selectActiveContextId, 'some-other-context');
|
||||
store.refreshState();
|
||||
|
||||
expect(service.isTodayListSignal()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -373,6 +373,11 @@ export class WorkContextService {
|
|||
map((id) => id === TODAY_TAG.id),
|
||||
shareReplay(1),
|
||||
);
|
||||
// Signal mirror of `isTodayList$`. The `isTodayList` boolean above is a plain
|
||||
// mutable field kept in sync via subscribe, so reading it inside a `computed()`
|
||||
// never invalidates the computed (a non-signal is not a producer, #8843). Reactive
|
||||
// consumers should read this signal instead; the boolean stays for synchronous reads.
|
||||
readonly isTodayListSignal = toSignal(this.isTodayList$, { initialValue: false });
|
||||
|
||||
isHasTasksToWorkOn$: Observable<boolean> = combineLatest([
|
||||
this.mainListTasks$,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue