mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
Merge pull request #8949 from aakhter/SPAP-40-review-hardening
fix(sync): suppress the "Project updated" snack on conflict-review flips
This commit is contained in:
commit
79e6a48ba6
5 changed files with 77 additions and 2 deletions
|
|
@ -31,7 +31,9 @@ export const addProjects = createAction(
|
|||
|
||||
export const updateProject = createAction(
|
||||
'[Project] Update Project',
|
||||
(projectProps: { project: Update<Project> }) => ({
|
||||
// isSkipSnack: suppress the unconditional "Project updated" snack — used by
|
||||
// flows that surface their own outcome (e.g. the conflict-review flip).
|
||||
(projectProps: { project: Update<Project>; isSkipSnack?: boolean }) => ({
|
||||
...projectProps,
|
||||
meta: {
|
||||
isPersistent: true,
|
||||
|
|
|
|||
|
|
@ -187,6 +187,35 @@ describe('ProjectEffects', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('snackUpdateBaseSettings$', () => {
|
||||
it('shows the "Project updated" snack for a normal updateProject', fakeAsync(() => {
|
||||
effects.snackUpdateBaseSettings$.subscribe();
|
||||
|
||||
actions$.next(
|
||||
updateProject({
|
||||
project: { id: 'project-1', changes: { title: 'New title' } },
|
||||
}),
|
||||
);
|
||||
|
||||
tick(100);
|
||||
expect(snackServiceSpy.open).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('suppresses the snack when isSkipSnack is set (conflict-review flip)', fakeAsync(() => {
|
||||
effects.snackUpdateBaseSettings$.subscribe();
|
||||
|
||||
actions$.next(
|
||||
updateProject({
|
||||
project: { id: 'project-1', changes: { title: 'Flipped title' } },
|
||||
isSkipSnack: true,
|
||||
}),
|
||||
);
|
||||
|
||||
tick(100);
|
||||
expect(snackServiceSpy.open).not.toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('moveAllProjectToTodayListWhenBacklogIsDisabled$', () => {
|
||||
it('should dispatch moveAllProjectBacklogTasksToRegularList when backlog is disabled', (done) => {
|
||||
effects.moveAllProjectToTodayListWhenBacklogIsDisabled$.subscribe((action: any) => {
|
||||
|
|
|
|||
|
|
@ -107,7 +107,8 @@ export class ProjectEffects {
|
|||
snackUpdateBaseSettings$: Observable<unknown> = createEffect(
|
||||
() =>
|
||||
this._actions$.pipe(
|
||||
ofType(updateProject.type),
|
||||
ofType(updateProject),
|
||||
filter((a) => !a.isSkipSnack),
|
||||
tap(() => {
|
||||
this._snackService.open({
|
||||
type: 'SUCCESS',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import { SnackService } from '../../core/snack/snack.service';
|
|||
import { EntityType } from '../core/operation.types';
|
||||
import { TaskSharedActions } from '../../root-store/meta/task-shared.actions';
|
||||
import { selectTaskById } from '../../features/tasks/store/task.selectors';
|
||||
import { selectProjectById } from '../../features/project/store/project.selectors';
|
||||
import { updateProject } from '../../features/project/store/project.actions';
|
||||
import { Project } from '../../features/project/project.model';
|
||||
import { Task } from '../../features/tasks/task.model';
|
||||
|
||||
const makeEntry = (over: Partial<ConflictJournalEntry> = {}): ConflictJournalEntry => ({
|
||||
|
|
@ -75,6 +78,13 @@ describe('SyncConflictUiService', () => {
|
|||
dispatchSpy = spyOn(store, 'dispatch').and.callThrough();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// overrideSelector mutates the SHARED selector references (selectTaskById /
|
||||
// selectProjectById) — without a reset the overrides leak into other spec
|
||||
// files in the same karma bundle (e.g. project.service.spec).
|
||||
store.resetSelectors();
|
||||
});
|
||||
|
||||
it('keep() marks the entry kept', async () => {
|
||||
const entry = makeEntry();
|
||||
await journal.record(entry);
|
||||
|
|
@ -382,6 +392,36 @@ describe('SyncConflictUiService', () => {
|
|||
expect(Object.prototype.hasOwnProperty.call(changes, 'notes')).toBe(false);
|
||||
});
|
||||
|
||||
it('flip() on a PROJECT entry suppresses the "Project updated" snack (isSkipSnack)', async () => {
|
||||
// The flip already reports its own outcome; without isSkipSnack the
|
||||
// unconditional snackUpdateBaseSettings$ effect would ALSO pop
|
||||
// "Project updated" on top of it.
|
||||
const entry = makeEntry({
|
||||
id: 'p1',
|
||||
entityType: 'PROJECT' as EntityType,
|
||||
entityId: 'project-1',
|
||||
});
|
||||
await journal.record(entry);
|
||||
store.overrideSelector(selectProjectById, {
|
||||
id: 'project-1',
|
||||
title: 'Remote title',
|
||||
} as Project);
|
||||
store.refreshState();
|
||||
|
||||
const result = await service.flip(entry);
|
||||
|
||||
expect(result).toBe('applied');
|
||||
const dispatched = dispatchSpy.calls.mostRecent().args[0] as ReturnType<
|
||||
typeof updateProject
|
||||
>;
|
||||
expect(dispatched.type).toBe(updateProject.type);
|
||||
expect(dispatched.project).toEqual({
|
||||
id: 'project-1',
|
||||
changes: { title: 'Local title' },
|
||||
});
|
||||
expect((dispatched as { isSkipSnack?: boolean }).isSkipSnack).toBe(true);
|
||||
});
|
||||
|
||||
it('canFlip() is false for delete-lost and delete-wins entries', () => {
|
||||
expect(service.canFlip(makeEntry({ reason: 'delete-lost', fieldDiffs: [] }))).toBe(
|
||||
false,
|
||||
|
|
|
|||
|
|
@ -280,6 +280,9 @@ export class SyncConflictUiService {
|
|||
id: entityId,
|
||||
changes: changes as Partial<Project>,
|
||||
} as Update<Project>,
|
||||
// The flip flow reports its own outcome — without this the
|
||||
// unconditional "Project updated" snack pops on top of it.
|
||||
isSkipSnack: true,
|
||||
});
|
||||
case 'NOTE':
|
||||
return updateNote({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue