apple ppo status | create --variant b[,c] | upload --variant b | start | stop: v2 experiments (platform IOS, traffic proportion), one treatment per shots variant (max 3), a treatment localization per App Store locale, and the variant renders synced into the treatment screenshot sets (shared MD5-idempotent set sync with apple screenshots, which no longer creates sets on --dry). start / stop ask first and are not MCP tools; MCP: apple_ppo_status, apple_ppo_create, apple_ppo_upload. Play experiments stay console-only (documented). apple.iphoneMedium (off by default): iphone-medium size key, 1206 x 2622 (1179 x 2556 accepted), uploaded as APP_IPHONE_61; rendered, checked, required and uploaded only when enabled. Apple's scaling rule documented in the config and the store-screenshots skill.
76 lines
5.7 KiB
TypeScript
76 lines
5.7 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import { displaySets } from '../cli/apple/screenshots';
|
|
import { checkCreate, createExperimentReq, defaultName, experimentFromApi, pickExperiment, planTreatmentLocales, planTreatments, startStopReq } from '../cli/lib/ppo';
|
|
import { validateConfig } from '../cli/lib/config';
|
|
import { FORBIDDEN_ARGS, TOOLS, toArgv, validateInput } from '../cli/mcp-tools';
|
|
import { requiredSets } from '../cli/shots/report';
|
|
import { defaultSizes, SIZES } from '../cli/shots/lib';
|
|
import { checkImage, IMAGE_SPECS, imageInfo } from '../cli/shots/spec';
|
|
import { png } from './fixtures';
|
|
|
|
describe('product page optimization', () => {
|
|
test('create options', () => {
|
|
expect(checkCreate(['b'], 30, ['b', 'c'])).toEqual([]);
|
|
expect(checkCreate([], 30, ['b']).join()).toContain('--variant');
|
|
expect(checkCreate(['b', 'c', 'd', 'e'], 30, ['b', 'c', 'd', 'e']).join()).toContain('at most 3');
|
|
expect(checkCreate(['z'], 30, ['b']).join()).toContain('unknown variant "z"');
|
|
expect(checkCreate(['b'], 0, ['b']).join()).toContain('--traffic');
|
|
expect(defaultName(['b', 'c'])).toBe('storebound b + c');
|
|
expect(createExperimentReq('app1', 'T', 30).body).toEqual({ data: { type: 'appStoreVersionExperiments', attributes: { name: 'T', platform: 'IOS', trafficProportion: 30 }, relationships: { app: { data: { type: 'apps', id: 'app1' } } } } });
|
|
});
|
|
test('picking a test', () => {
|
|
const list = [
|
|
experimentFromApi({ id: 'e1', attributes: { name: 'old', state: 'COMPLETED' } }),
|
|
experimentFromApi({ id: 'e2', attributes: { name: 'new', state: 'PREPARE_FOR_SUBMISSION', trafficProportion: 30 } }),
|
|
];
|
|
expect(pickExperiment(list)!.id).toBe('e2');
|
|
expect(pickExperiment(list, 'new')!.trafficProportion).toBe(30);
|
|
expect(pickExperiment(list, 'missing')).toBeNull();
|
|
expect(() => pickExperiment(list, 'old')).toThrow('cannot be restarted');
|
|
expect(() => pickExperiment([...list, experimentFromApi({ id: 'e3', attributes: { name: 'x', state: 'READY_FOR_REVIEW' } })])).toThrow('--experiment');
|
|
});
|
|
test('treatments and localizations', () => {
|
|
const t = planTreatments('e2', [{ id: 't1', name: 'b' }], ['b', 'c']);
|
|
expect(t).toHaveLength(1);
|
|
expect(t[0]!.body).toEqual({ data: { type: 'appStoreVersionExperimentTreatments', attributes: { name: 'c' }, relationships: { appStoreVersionExperimentV2: { data: { type: 'appStoreVersionExperiments', id: 'e2' } } } } });
|
|
expect(() => planTreatments('e2', [{ id: '1', name: 'a' }, { id: '2', name: 'b' }, { id: '3', name: 'c' }], ['d'])).toThrow('at most 3');
|
|
const l = planTreatmentLocales('t1', ['en-US'], ['en-US', 'tr']);
|
|
expect(l.map(r => (r.body as { data: { attributes: { locale: string } } }).data.attributes.locale)).toEqual(['tr']);
|
|
expect((l[0]!.body as { data: { relationships: unknown } }).data.relationships).toEqual({ appStoreVersionExperimentTreatment: { data: { type: 'appStoreVersionExperimentTreatments', id: 't1' } } });
|
|
});
|
|
test('start / stop', () => {
|
|
const e = experimentFromApi({ id: 'e2', attributes: { name: 'new', state: 'PREPARE_FOR_SUBMISSION' } });
|
|
expect(startStopReq(e, true)).toMatchObject({ method: 'PATCH', path: '/v2/appStoreVersionExperiments/e2', body: { data: { type: 'appStoreVersionExperiments', id: 'e2', attributes: { started: true } } } });
|
|
expect(() => startStopReq({ ...e, state: 'STOPPED' }, false)).toThrow('already STOPPED');
|
|
expect(() => startStopReq({ ...e, state: 'COMPLETED' }, true)).toThrow('cannot start');
|
|
});
|
|
test('mcp: status / create / upload, never start or stop', () => {
|
|
const t = (n: string) => TOOLS.find(x => x.name === n)!;
|
|
expect(t('apple_ppo_status').write).toBe(false);
|
|
expect(toArgv(t('apple_ppo_create'), { variants: 'b,c', traffic: 20 }).argv).toEqual(['apple', 'ppo', 'create', '--variant', 'b,c', '--traffic', '20', '--dry']);
|
|
expect(toArgv(t('apple_ppo_upload'), { variant: 'b', only: 'en-US' }).argv).toEqual(['apple', 'ppo', 'upload', '--variant', 'b', '--only', 'en-US', '--dry']);
|
|
expect(validateInput(t('apple_ppo_create'), { variants: 'a,b,c,d' })[0]).toContain('does not match');
|
|
expect(FORBIDDEN_ARGS).toEqual(expect.arrayContaining(['start', 'stop']));
|
|
});
|
|
});
|
|
|
|
describe('optional 6.3" iPhone size (iphone-medium)', () => {
|
|
const base = { app: { name: 'X', privacyPolicyUrl: 'https://example.com/p', supportUrl: 'https://example.com', website: 'https://example.com', contactEmail: 'a@example.com' }, languages: [{ id: 'en', apple: 'en-US' }] };
|
|
test('off by default, on with apple.iphoneMedium', () => {
|
|
expect(SIZES['iphone-medium']).toEqual({ w: 1206, h: 2622 });
|
|
expect(defaultSizes({ apple: { bundleId: 'x' } })).not.toContain('iphone-medium');
|
|
expect(defaultSizes({ apple: { bundleId: 'x', iphoneMedium: true } })).toContain('iphone-medium');
|
|
expect(defaultSizes({ play: { packageName: 'x' } })).not.toContain('iphone-medium');
|
|
expect(Object.keys(displaySets({}))).toEqual(['ios69', 'ios65']);
|
|
expect(displaySets({ iphoneMedium: true, ipad: true })).toEqual({ ios69: 'APP_IPHONE_67', ios65: 'APP_IPHONE_65', 'iphone-medium': 'APP_IPHONE_61', ipad13: 'APP_IPAD_PRO_3GEN_129' });
|
|
expect(requiredSets({ apple: {} })['iphone-medium']).toBeUndefined();
|
|
expect(requiredSets({ apple: { iphoneMedium: true } })['iphone-medium']).toBe(true);
|
|
expect(validateConfig({ ...base, apple: { bundleId: 'com.example.game', iphoneMedium: true } })).toEqual([]);
|
|
});
|
|
test('accepted pixel sizes', () => {
|
|
expect(IMAGE_SPECS['iphone-medium']!.type).toBe('APP_IPHONE_61');
|
|
expect(checkImage(imageInfo(png(1206, 2622)), 'iphone-medium')).toEqual([]);
|
|
expect(checkImage(imageInfo(png(1179, 2556)), 'iphone-medium')).toEqual([]);
|
|
expect(checkImage(imageInfo(png(1320, 2868)), 'iphone-medium').map(i => i.code)).toEqual(['size']);
|
|
});
|
|
});
|