Desktop: per-store ignore (hide only the App Store or Google Play side), N ignored show toggle in sidebar and portfolio, Unignore in the app controls; ignore flags sync with the shared app list

This commit is contained in:
CRTkafa 2026-10-23 01:35:27 +08:00
parent 13b91dd423
commit 9610c9477e
14 changed files with 347 additions and 17 deletions

View file

@ -4,7 +4,7 @@ import { describe, expect, test } from 'bun:test';
import type { Account, Project, ProjectSummary, StoreApp } from '../src/api';
import {
buildApps, filterSort, groupByStudio, type ListRow, NO_OVERRIDES, normId, normName, type Overrides, pairCandidates, storeTarget, studioCounts,
withIgnored, withLink, withPair, withStudio, withUnpair,
withIgnored, withLink, withPair, withSideIgnored, withStudio, withUnpair,
} from '../src/lib/identity';
import { crashCrossed, newLowReviews, releaseChanges } from '../src/lib/notify';
@ -73,6 +73,37 @@ describe('identity pairing', () => {
expect([linked.ios?.app.id, linked.android?.app.id, linked.pairing]).toEqual(['102', 'com.example.orbit', 'folder']);
expect(apps.some(a => a.name === 'Orbit Hop' && !a.project)).toBe(false);
});
test('ignore one store side: a folder hides only that store, both sides is the whole app, restore splits it', () => {
const base = byName(build(), 'Prism Puddle');
expect([base.ios?.app.id, base.android?.app.id, base.ignored, base.ignoredStores]).toEqual(['101', 'com.example.prism', false, []]);
const noIos = withSideIgnored(NO_OVERRIDES, base, 'ios', true);
expect(noIos.ignored).toEqual(['project:p1:ios']);
const half = byName(build(noIos), 'Prism Puddle');
expect([half.ios, half.android?.app.id, half.ignored, half.ignoredStores]).toEqual([null, 'com.example.prism', false, ['ios']]);
expect(half.sideKeys.ios).toBe('project:p1:ios');
// the hidden iOS app does not come back as a store-only app of its own
expect(build(noIos).filter(a => a.ios?.app.id === '101')).toHaveLength(0);
const both = withSideIgnored(noIos, half, 'android', true);
expect(both.ignored).toEqual(['project:p1']);
const whole = byName(build(both), 'Prism Puddle');
expect([whole.ignored, whole.ignoredStores]).toEqual([true, []]);
const back = withSideIgnored(both, whole, 'ios', false);
expect(back.ignored.sort()).toEqual(['project:p1:android']);
expect(byName(build(back), 'Prism Puddle').ignoredStores).toEqual(['android']);
// restoring the whole app clears every key of it
expect(withIgnored(both, whole.ignoreKeys, false).ignored).toEqual([]);
});
test('ignore one side of a paired store-only app; ignoring both sides ignores the app', () => {
const orbit = byName(build(), 'Orbit Hop');
expect(orbit.ios && orbit.android).toBeTruthy();
const o = withSideIgnored(NO_OVERRIDES, orbit, 'android', true);
const half = byName(build(o), 'Orbit Hop');
expect([half.ios?.app.id, half.android, half.ignored, half.ignoredStores]).toEqual(['102', null, false, ['android']]);
const all = withSideIgnored(o, half, 'ios', true);
const full = byName(build(all), 'Orbit Hop');
expect(full.ignored).toBe(true);
expect(full.ios && full.android).toBeTruthy();
});
test('ignore: an app (all its store keys) or a whole account; studio moves', () => {
const tycoon = byName(build(), 'Pocket Tycoon');
const o = withIgnored(NO_OVERRIDES, tycoon.ignoreKeys, true);