112 lines
7.9 KiB
TypeScript
112 lines
7.9 KiB
TypeScript
// apple.macos: per-platform listing file and review notes for an app with an iOS and a macOS App Store version, the
|
|
// config check, and the validate warning for iOS-only claims in a macOS version's texts.
|
|
import { describe, expect, test } from 'bun:test';
|
|
import { isMacosPlatform, listingPathFor, reviewNotesFor, validateExtras, type ShipConfig } from '../cli/lib/config';
|
|
import { reviewDetailPlan, reviewNotes } from '../cli/lib/review-details';
|
|
import { appleRules, macosClaimRules, type AppleSnapshot } from '../cli/lib/validate-rules';
|
|
|
|
const cfg = (apple: Record<string, unknown>) => ({ apple: { bundleId: 'com.example.app', ...apple } }) as unknown as Pick<ShipConfig, 'apple' | 'reviewAccess' | 'accounts'>;
|
|
const contact = { contactFirstName: 'A', contactLastName: 'B', contactPhone: '+10000000000', contactEmail: 'a@example.com' };
|
|
|
|
describe('platform helpers', () => {
|
|
test('spellings of macOS', () => {
|
|
for (const p of ['macos', 'MAC_OS', 'macOS', 'mac']) expect(isMacosPlatform(p)).toBe(true);
|
|
for (const p of ['ios', 'IOS', 'tvos', '', undefined]) expect(isMacosPlatform(p)).toBe(false);
|
|
});
|
|
test('listingPathFor: macOS override, else apple.listing, else the default', () => {
|
|
const c = cfg({ listing: 'store/ios.md', macos: { listing: 'store/mac.md' } });
|
|
expect(listingPathFor(c, 'macos')).toBe('store/mac.md');
|
|
expect(listingPathFor(c, 'MAC_OS')).toBe('store/mac.md');
|
|
expect(listingPathFor(c, 'ios')).toBe('store/ios.md');
|
|
expect(listingPathFor(c)).toBe('store/ios.md');
|
|
expect(listingPathFor(cfg({ macos: {} }), 'macos')).toBe('store/appstore.md');
|
|
expect(listingPathFor(cfg({ listing: 'store/ios.md', macos: { reviewNotes: 'x' } }), 'macos')).toBe('store/ios.md');
|
|
});
|
|
test('reviewNotesFor: macOS override, else apple.info.reviewNotes', () => {
|
|
const c = cfg({ info: { reviewNotes: 'iOS notes' }, macos: { reviewNotes: 'Mac notes' } });
|
|
expect(reviewNotesFor(c, 'macos')).toBe('Mac notes');
|
|
expect(reviewNotesFor(c, 'ios')).toBe('iOS notes');
|
|
expect(reviewNotesFor(cfg({ info: { reviewNotes: 'iOS notes' }, macos: {} }), 'macos')).toBe('iOS notes');
|
|
});
|
|
});
|
|
|
|
describe('review details plan', () => {
|
|
const c = { ...cfg({ info: { reviewNotes: 'iOS notes: Game Center, IAP' }, macos: { reviewNotes: 'Mac notes: no purchases' } }), reviewAccess: { signInRequired: false, instructions: 'No sign-in.' } } as Pick<ShipConfig, 'apple' | 'reviewAccess' | 'accounts'>;
|
|
test('macos picks the Mac notes, ios and default keep the iOS notes; the shared sign-in line stays', () => {
|
|
expect(reviewDetailPlan(c, contact, null, 'macos').attributes.notes).toBe('Mac notes: no purchases\n\nSign-in: No sign-in.');
|
|
expect(reviewDetailPlan(c, contact, null, 'ios').attributes.notes).toBe('iOS notes: Game Center, IAP\n\nSign-in: No sign-in.');
|
|
expect(reviewDetailPlan(c, contact, null).attributes.notes).toBe(reviewNotes(c));
|
|
});
|
|
});
|
|
|
|
describe('config check', () => {
|
|
test('apple.macos shape', () => {
|
|
expect(validateExtras({ apple: { bundleId: 'com.example.app', macos: { listing: 'a.md', reviewNotes: 'x', gameCenter: true, noIap: true } } } as never)).toEqual([]);
|
|
const bad = validateExtras({ apple: { bundleId: 'com.example.app', macos: { listing: '', gameCenter: 'yes', noIap: 1 } } } as never);
|
|
expect(bad.some(m => m.startsWith('apple.macos.listing'))).toBe(true);
|
|
expect(bad.some(m => m.startsWith('apple.macos.gameCenter'))).toBe(true);
|
|
expect(bad.some(m => m.startsWith('apple.macos.noIap'))).toBe(true);
|
|
expect(validateExtras({ apple: { bundleId: 'com.example.app', macos: 'x' } } as never).some(m => m.startsWith('apple.macos:'))).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('validate: iOS-only claims on a macOS version', () => {
|
|
const loc = (o: Record<string, string>) => [{ locale: 'en-US', screenshots: {}, ...o }];
|
|
const snap = (over: Partial<AppleSnapshot>): Pick<AppleSnapshot, 'version' | 'locales' | 'reviewDetail' | 'macos'> =>
|
|
({ version: { versionString: '1.0.0', state: 'PREPARE_FOR_SUBMISSION', platform: 'MAC_OS' }, locales: [], reviewDetail: null, ...over });
|
|
const codes = (f: { msg: string }[]) => f.map(x => x.msg);
|
|
|
|
test('Game Center, touch words and iPhone mentions warn; nothing blocks', () => {
|
|
const f = macosClaimRules(snap({ locales: loc({ description: 'Compete on Game Center. Tap and swipe to play on your iPhone.' }) }));
|
|
expect(f.length).toBe(3);
|
|
expect(f.every(x => x.level === 'warn' && x.code === 'macos-claims')).toBe(true);
|
|
expect(codes(f).join('\n')).toContain('en-US description');
|
|
});
|
|
test('promotional text, what\'s new, keywords and live review notes are read', () => {
|
|
const f = macosClaimRules(snap({ locales: loc({ promotionalText: 'Use the touch screen', whatsNew: 'Pinch to zoom', keywords: 'ipad' }), reviewDetail: { notes: 'Sign in with Game Center' } }));
|
|
const all = codes(f).join('\n');
|
|
expect(all).toContain('en-US promotionalText');
|
|
expect(all).toContain('en-US whatsNew');
|
|
expect(all).toContain('en-US keywords');
|
|
expect(all).toContain('review notes (on the store)');
|
|
});
|
|
test('the config review notes the Mac would use are checked too', () => {
|
|
expect(codes(macosClaimRules(snap({ macos: { notes: 'Game Center leaderboards are in the menu' } }))).join()).toContain('review notes (config)');
|
|
});
|
|
test('apple.macos.gameCenter silences Game Center; IAP lines only with noIap', () => {
|
|
const text = loc({ description: 'Game Center scores. Remove ads with an in-app purchase.' });
|
|
expect(macosClaimRules(snap({ locales: text, macos: { gameCenter: true } }))).toEqual([]);
|
|
const f = macosClaimRules(snap({ locales: text, macos: { gameCenter: true, noIap: true } }));
|
|
expect(f.length).toBe(1);
|
|
expect(f[0]!.msg).toContain('in-app purchase');
|
|
});
|
|
test('words that only look similar do not match; iOS versions are never checked', () => {
|
|
expect(macosClaimRules(snap({ locales: loc({ description: 'Contact the support team; a stapler, a tapestry, an iPhoneless desk.' }) }))).toEqual([]);
|
|
expect(macosClaimRules(snap({ version: { versionString: '1.0.0', state: 'PREPARE_FOR_SUBMISSION', platform: 'IOS' }, locales: loc({ description: 'Game Center, tap' }) }))).toEqual([]);
|
|
});
|
|
test('appleRules includes the warning', () => {
|
|
const base = { expectedLocales: [], requiredDisplayTypes: [], iaps: [], expectedProducts: [], ageRating: null, priceSchedule: false, availability: false, build: null, privacyUrl: { url: 'https://example.com', status: 200 } };
|
|
const f = appleRules({ ...base, ...snap({ locales: loc({ description: 'Tap here', screenshots: {} as never }) }) } as AppleSnapshot);
|
|
expect(f.some(x => x.code === 'macos-claims' && x.level === 'warn')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('apple listing reads the platform file', () => {
|
|
test('readAppleCopy(ctx, "macos") reads apple.macos.listing, ios reads apple.listing', async () => {
|
|
const { mkdtempSync, writeFileSync } = await import('node:fs');
|
|
const { tmpdir } = await import('node:os');
|
|
const { join, resolve } = await import('node:path');
|
|
const { readAppleCopy } = await import('../cli/apple/listing');
|
|
const { parseArgs } = await import('../cli/lib/args');
|
|
const fence = '`'.repeat(3);
|
|
const sec = (d: string) => ['## en-US', '', '### subtitle', fence, 'Sub', fence, '', '### promotionalText', fence, 'Promo', fence, '', '### keywords', fence, 'run,time', fence, '', '### description', fence, d, fence, ''].join('\n');
|
|
const dir = mkdtempSync(join(tmpdir(), 'sb-macos-'));
|
|
writeFileSync(join(dir, 'ios.md'), sec('Play on iPhone with Game Center.'));
|
|
writeFileSync(join(dir, 'mac.md'), sec('Play on your Mac.'));
|
|
const loaded = { config: cfg({ listing: 'ios.md', macos: { listing: 'mac.md' } }), resolve: (p: string) => resolve(dir, p), root: dir };
|
|
const ctx = { loaded, args: parseArgs([]) } as never;
|
|
expect(readAppleCopy(ctx, 'macos')['en-US']?.description).toContain('Mac');
|
|
expect(readAppleCopy(ctx, 'ios')['en-US']?.description).toContain('iPhone');
|
|
expect(readAppleCopy(ctx)['en-US']?.description).toContain('iPhone');
|
|
});
|
|
});
|