Onboarding live apps without owner questions for what the stores and the code prove: storebound onboard imports the live products and subscriptions with USD / base prices (catalog diff clean), contact values, the App Review contact and demo account (credentials.json, masked), sign-in for review from Apple's review detail and auth code, and the account deletion page from the app's site; App Store keywords count characters (live fields up to 265 bytes accepted); validate accepts the 6.5-inch iPhone set and Play's default-language graphics fallback; Play Reporting API off gives the project's enable link and apps list falls back to local apps; accounts default <profile>; store-only apps (apps add --store --app-id) shown read-only in the desktop (pitfalls 43-52)
This commit is contained in:
parent
d2ce185441
commit
e5f148e534
55 changed files with 1648 additions and 158 deletions
|
|
@ -85,7 +85,7 @@ describe('mcp tools', () => {
|
|||
}
|
||||
}
|
||||
// the local-write exception covers only the pull tools, and only with dry_run false
|
||||
expect(Object.keys(LOCAL_WRITE_TOOLS).sort()).toEqual(['apple_listing_pull', 'catalog_pull', 'play_listing_pull']);
|
||||
expect(Object.keys(LOCAL_WRITE_TOOLS).sort()).toEqual(['apple_listing_pull', 'catalog_pull', 'onboard', 'play_listing_pull']);
|
||||
for (const n of Object.keys(LOCAL_WRITE_TOOLS)) expect(toArgv(TOOLS.find(t => t.name === n)!, {}).argv).not.toContain('--write');
|
||||
const phased = TOOLS.find(t => t.name === 'apple_phased_release')!;
|
||||
expect(phased.props!.action!.enum).toEqual(['on', 'pause']);
|
||||
|
|
@ -148,18 +148,20 @@ describe('mcp: screenshot and video tools', () => {
|
|||
});
|
||||
|
||||
describe('aso', () => {
|
||||
test('App Store keywords count bytes, not characters (limit and fill hint)', () => {
|
||||
const msgs = (kw: string) => auditAppleKeywords('ru', { keywords: kw } as never, 'X').map(f => f.msg);
|
||||
const short = 'игра,бег,прыжки,монеты,уровни,рекорд,аркада'; // 43 characters, 80 bytes
|
||||
expect(msgs(short).some(m => m.includes('80/100 bytes') && m.includes('fill at least 90'))).toBe(true);
|
||||
const full = short + ',быстро'; // 50 characters but 93 bytes: full enough
|
||||
expect(msgs(full).some(m => m.includes('fill at least 90'))).toBe(false);
|
||||
expect(msgs(full + ',звук').some(m => m.includes('102 bytes > 100'))).toBe(true);
|
||||
test('App Store keywords count characters, not bytes (limit and fill hint; pitfall 43)', () => {
|
||||
const msgs = (kw: string) => auditAppleKeywords('pl', { keywords: kw } as never, 'X').map(f => f.msg);
|
||||
// live shape: a Polish field of 99 characters / 105 bytes, READY_FOR_SALE
|
||||
const pl = 'ryby,akwarium,ocean,morze,rekin,koralowiec,nurkowanie,relaks,kolekcja,hodowla,pływanie,łowienie,żółw';
|
||||
expect([...pl].length).toBeLessThanOrEqual(100);
|
||||
expect(Buffer.byteLength(pl)).toBeGreaterThan(100);
|
||||
expect(msgs(pl).filter(m => /> 100/.test(m))).toEqual([]);
|
||||
expect(msgs('ryby,akwarium').some(m => m.includes('13/100 characters used') && m.includes('fill at least 90'))).toBe(true);
|
||||
expect(msgs('ż'.repeat(101)).some(m => m.includes('101 characters > 100'))).toBe(true);
|
||||
});
|
||||
test('keyword field rules', () => {
|
||||
const f = auditAppleKeywords('en-US', { subtitle: 'Relaxing puzzle', keywords: 'match,puzzle,match,free game' }, 'Tile Match');
|
||||
const text = f.map(x => `${x.level}:${x.msg}`).join('\n');
|
||||
expect(text).toContain('warn:keywords: 28/100 bytes used');
|
||||
expect(text).toContain("warn:keywords: 28/100 characters used");
|
||||
expect(text).toContain('"match" listed twice');
|
||||
expect(text).toContain('repeat words from the name / subtitle (already indexed): match, puzzle');
|
||||
expect(text).toContain('filler words that rarely help: free, game');
|
||||
|
|
@ -324,6 +326,35 @@ describe('validate rules', () => {
|
|||
h.tracks = [{ track: 'production', releases: [{ status: 'halted', versionCodes: ['8'], userFraction: 0.1 }] }];
|
||||
expect(playRules(h).find(x => x.code === 'tracks')!.level).toBe('warn');
|
||||
});
|
||||
test('play: a non-default language without graphics uses the default language\'s (pitfall 45)', () => {
|
||||
// live shape: 18 listings, only the default language (tr-TR) has an icon and a feature graphic
|
||||
const s = playOk();
|
||||
const extra = ['en-US', 'de-DE', 'fr-FR', 'es-ES', 'it-IT', 'pt-BR', 'ru-RU', 'ja-JP', 'ko-KR', 'zh-CN', 'ar', 'hi-IN', 'id', 'pl-PL', 'nl-NL', 'th', 'vi'];
|
||||
s.defaultLanguage = 'tr-TR';
|
||||
s.expectedLanguages = ['tr-TR', ...extra];
|
||||
s.listings = [{ language: 'tr-TR', title: 't', shortDescription: 's', fullDescription: 'f', images: { icon: 1, featureGraphic: 1, phoneScreenshots: 4 } },
|
||||
...extra.map(language => ({ language, title: 't', shortDescription: 's', fullDescription: 'f', images: { icon: 0, featureGraphic: 0, phoneScreenshots: 4 } }))];
|
||||
const f = playRules(s);
|
||||
expect(f.filter(x => x.level === 'blocker')).toEqual([]);
|
||||
expect(f.filter(x => x.code === 'images-fallback').map(x => x.msg)).toEqual([
|
||||
"icon: 17 language(s) without their own use the default language's (tr-TR) graphics: en-US, de-DE, fr-FR, es-ES, it-IT, pt-BR ...",
|
||||
"featureGraphic: 17 language(s) without their own use the default language's (tr-TR) graphics: en-US, de-DE, fr-FR, es-ES, it-IT, pt-BR ...",
|
||||
]);
|
||||
// the default language's own missing graphics still block
|
||||
s.listings[0]!.images.featureGraphic = 0;
|
||||
expect(playRules(s).filter(x => x.level === 'blocker').map(x => x.msg)).toEqual(expect.arrayContaining(['tr-TR: featureGraphic 0/1']));
|
||||
expect(playRules(s).filter(x => x.level === 'blocker').length).toBe(18);
|
||||
});
|
||||
test('apple: a live app with only the 6.5-inch set passes; 6.9-inch is a recommendation (pitfall 44)', () => {
|
||||
const s = appleOk();
|
||||
// live shape: GET /v1/appStoreVersionLocalizations/{id}/appScreenshotSets answered one APP_IPHONE_65 set per locale
|
||||
s.locales = s.locales.map(l => ({ ...l, screenshots: { APP_IPHONE_65: 6 } }));
|
||||
const f = appleRules(s);
|
||||
expect(f.filter(x => x.level === 'blocker')).toEqual([]);
|
||||
expect(f.filter(x => x.code === 'screenshots-size').map(x => `${x.level}:${x.msg}`)[0]).toBe('warn:en-US: no APP_IPHONE_67 set; Apple accepts the APP_IPHONE_65 set (6 screenshots) and scales it for the other sizes. Recommended, not required: add APP_IPHONE_67');
|
||||
s.locales[0]!.screenshots = { APP_IPHONE_61: 3 };
|
||||
expect(appleRules(s).filter(x => x.level === 'blocker').map(x => x.msg)).toEqual(['en-US: no APP_IPHONE_67 screenshots (or APP_IPHONE_65)']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('release helpers', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue