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
|
|
@ -153,20 +153,21 @@ describe('candidates and the 100-byte field', () => {
|
|||
expect(c.find(x => x.term === 'shopping')).toMatchObject({ popularity: 64, popularityFrom: 'exact', sources: ['competitors', 'apple-ads'] });
|
||||
expect(c.find(x => x.term === 'aisle')!.popularity).toBeNull();
|
||||
});
|
||||
test('fit: <= 100 bytes, current terms kept, indexed and duplicate words dropped', () => {
|
||||
test('fit: <= 100 characters, current terms kept, indexed and duplicate words dropped', () => {
|
||||
const ranked = candidates({ ...base, ads: [] });
|
||||
const p = fitKeywordField({ locale: 'en-US', current: base.keywords, name: base.name, subtitle: base.subtitle, ranked });
|
||||
expect(p.kept).toEqual(['todo', 'checklist', 'family']);
|
||||
expect(p.dropped).toEqual([{ term: 'list', why: 'already in the name / subtitle (indexed)' }]);
|
||||
expect(p.added).toContain('packing');
|
||||
expect(p.after).not.toMatch(/,\s|\s,/);
|
||||
expect(p.bytes).toBeLessThanOrEqual(100);
|
||||
expect(p.chars).toBeLessThanOrEqual(100);
|
||||
expect(new Set(p.after.split(',')).size).toBe(p.after.split(',').length);
|
||||
// bytes, not characters: Turkish letters take two
|
||||
// characters, not bytes: Turkish letters take two bytes and still count one (pitfall 43)
|
||||
const many = Array.from({ length: 30 }, (_, i) => ({ term: `şğü${i}`, kind: 'word' as const, sources: ['description' as const], ours: 2, relevance: 1, inNames: 0, ofApps: 0, popularity: null, popularityFrom: null, status: 'candidate' as const }));
|
||||
const t = fitKeywordField({ locale: 'tr', current: '', name: 'X', subtitle: '', ranked: many });
|
||||
expect(t.bytes).toBeLessThanOrEqual(100);
|
||||
expect(Buffer.byteLength(t.after)).toBe(t.bytes);
|
||||
expect(t.chars).toBeLessThanOrEqual(100);
|
||||
expect([...t.after].length).toBe(t.chars);
|
||||
expect(Buffer.byteLength(t.after)).toBeGreaterThan(t.chars);
|
||||
expect(fitKeywordField({ locale: 'en-US', current: 'todo', name: 'X', subtitle: '', ranked, fresh: true }).kept).toEqual([]);
|
||||
});
|
||||
test('write: keywords block replaced, draft line above it, found again, then removable', () => {
|
||||
|
|
@ -202,7 +203,7 @@ describe('audit parts', () => {
|
|||
expect(u.stuffed).toContain('grocery');
|
||||
});
|
||||
test('audit: next 3 actions, PPO cannot test the subtitle, Play experiments are a guide', () => {
|
||||
const a = buildAudit({ today: '2026-10-12', storefront: 'US', findings: [{ level: 'warn', where: 'apple en-US', msg: 'keywords: 40/100 bytes used; fill at least 90 (60 bytes free)' }],
|
||||
const a = buildAudit({ today: '2026-10-12', storefront: 'US', findings: [{ level: 'warn', where: 'apple en-US', msg: 'keywords: 40/100 characters used; fill at least 90 (60 characters free)' }],
|
||||
drafts: [], appName: 'Example Notes', ranks: [], competitors: null, analytics: [], variants: ['b'],
|
||||
apple: { 'en-US': { subtitle: 'x', keywords: 'a' } }, play: [{ lang: 'en-US', title: 'T', short: 'Short', full: 'Full text.', whatsNew: '' }] });
|
||||
expect(a.next).toHaveLength(3);
|
||||
|
|
@ -242,7 +243,7 @@ describe('commands', () => {
|
|||
const p = project(BASE, files);
|
||||
mockFetch([]);
|
||||
let r = await capture(() => aso(p.ctx(['suggest', '--locale', 'en-US', '--json'])));
|
||||
const j = jsonOut(r.out) as { written: boolean; results: { ads: string; proposal: { before: string; after: string; bytes: number }; candidates: { popularity: number | null }[] }[] };
|
||||
const j = jsonOut(r.out) as { written: boolean; results: { ads: string; proposal: { before: string; after: string; chars: number }; candidates: { popularity: number | null }[] }[] };
|
||||
expect(j.written).toBe(false);
|
||||
expect(j.results[0]!.ads).toMatch(/no Apple Ads credentials.*popularity unknown/);
|
||||
expect(j.results[0]!.candidates.every(c => c.popularity === null)).toBe(true);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue