play: a new language is created with everything Play needs to commit it (icon, feature graphic, phone and tablet screenshots); play images creates a missing listing

This commit is contained in:
CRTkafa 2026-10-24 19:46:57 +08:00
parent 2ec1f0ab13
commit ed8d138a8e
7 changed files with 172 additions and 32 deletions

View file

@ -370,6 +370,7 @@ describe('listing, appinfo, data safety and the owner commands: a second run wri
test('play listing: one committed edit with only what differs; the second run opens no write edit and commits nothing', async () => {
s.play.live.listings['de-DE'] = { language: 'de-DE', title: 'Notizen' };
s.play.live.listings['en-US'] = { language: 'en-US', title: 'Old' };
const p = project(CONFIG, { 'store/listing.md': LISTING });
const r = await twice(() => playListing(p.ctx([])));
expect(r.first).toEqual(['PUT /androidpublisher/v3/applications/com.example.notes/edits/edit-2/details', 'PUT /androidpublisher/v3/applications/com.example.notes/edits/edit-2/listings/en-US',
@ -380,6 +381,71 @@ describe('listing, appinfo, data safety and the owner commands: a second run wri
expect(s.play.live.listings['de-DE']).toEqual({ language: 'de-DE', title: 'Notizen' }); // a language not in the file is left alone
});
// A new Play language (tr-TR next to a live en-US): the commit is refused 403 "too few screenshots" unless the
// screenshots, icon and feature graphic travel in the same edit (pitfall 102)
const trConfig = (): ShipConfig => ({ ...CONFIG, languages: [{ id: 'en', play: 'en-US' }, { id: 'tr', play: 'tr-TR' }] as never });
const trListing = LISTING + ['', '## Turkish', '<!-- play: tr-TR -->', '', '### Play short description', '```', 'Notlar.', '```', '', '### Play full description', '```', 'Herkes icin notlar.', '```', ''].join('\n');
const renders = (lang: string, base: number, tablets = false): Record<string, Uint8Array> => {
const f: Record<string, Uint8Array> = { [`store/renders/${lang}/feature/1024x500.png`]: png(base) };
for (const n of [1, 2]) { f[`store/renders/${lang}/play/0${n}.png`] = png(base + n); if (tablets) for (const t of ['tab7', 'tab10']) f[`store/renders/${lang}/${t}/0${n}.png`] = png(base + n + 5); }
return f;
};
const liveEn = () => {
s.play.live.listings['en-US'] = { language: 'en-US', title: 'Example Notes', shortDescription: 'Notes.', fullDescription: 'Notes for everyone.' };
s.play.live.details = { defaultLanguage: 'en-US', contactEmail: 'support@example.com', contactWebsite: 'https://example.com' };
s.play.requireNewLangAssets = true;
};
test('play listing: a new language uploads icon, feature graphic and phone screenshots in the same edit, so the commit passes', async () => {
liveEn();
const p = project(trConfig(), { 'store/listing.md': trListing, 'store/icons/play-512.png': png(1), ...renders('tr', 40) });
const r = await capture(() => playListing(p.ctx([])));
expect(r.exit).toBe(null);
expect(s.play.committed).toHaveLength(1);
expect(s.play.live.images['tr-TR/phoneScreenshots']).toHaveLength(2);
expect(s.play.live.images['tr-TR/icon']).toHaveLength(1);
expect(s.play.live.images['tr-TR/featureGraphic']).toHaveLength(1);
});
test('play listing: a new language without its own screenshots uses the default language images; none anywhere is refused in the dry run, nothing written', async () => {
liveEn();
const base = { 'store/listing.md': trListing, 'store/icons/play-512.png': png(1) };
const ok = project(trConfig(), { ...base, ...renders('en', 10) });
const r1 = await capture(() => playListing(ok.ctx([])));
expect(r1.exit).toBe(null);
expect(r1.out).toContain("no phoneScreenshots of its own, using en-US's");
expect(s.play.live.images['tr-TR/phoneScreenshots']).toHaveLength(2);
s.play.live.listings = { 'en-US': s.play.live.listings['en-US']! };
s.play.live.images = {};
const bad = project(trConfig(), base);
const before = s.play.committed.length;
const mark = s.calls.length;
await expect(playListing(bad.ctx(['--dry']))).rejects.toThrow('tr-TR: no phoneScreenshots');
expect(s.play.committed).toHaveLength(before);
expect(writes(s.calls.slice(mark))).toEqual([]);
});
test('play listing: the dry run names the uploads of a new language; tablet sets are required when the default language has them on Play', async () => {
liveEn();
s.play.requireTablets = true;
s.play.live.images['en-US/sevenInchScreenshots'] = ['x', 'y'];
const p = project(trConfig(), { 'store/listing.md': trListing, 'store/icons/play-512.png': png(1), ...renders('tr', 40), ...renders('en', 10, true) });
const r = await capture(() => playListing(p.ctx(['--dry'])));
expect(r.out).toContain('tr-TR is new on Play: uploads');
expect(r.out).toContain('sevenInchScreenshots (2)');
expect(s.play.committed).toEqual([]);
});
test('play images: a language with text in the listing file but no Play listing yet is created with its images, commit passes', async () => {
liveEn();
const p = project(trConfig(), { 'store/listing.md': trListing, 'store/icons/play-512.png': png(1), ...renders('en', 10), ...renders('tr', 40) });
const r = await capture(() => playImages(p.ctx([])));
expect(r.exit).toBe(null);
expect(r.out).toContain('tr-TR: no Play listing yet, this run creates it');
expect(s.play.live.listings['tr-TR']!.title).toBeDefined();
expect(s.play.live.images['tr-TR/phoneScreenshots']).toHaveLength(2);
});
test('play listing: the store reached the wanted state between plan and edit: the edit is deleted, not committed', async () => {
const p = project(CONFIG, { 'store/listing.md': LISTING });
// the plan sees an empty listing; right after it (before the write edit opens) another run makes it current