check blocks an apple app without app.copyright (App Store Connect requires it; never filled in automatically); validate reads the live version's copyright and says whether the store has one; round-trip test that the config writers (upsertBlock, upsertNestedString, removeKeyPath) keep unknown keys and comments; tests
This commit is contained in:
parent
73db689386
commit
e9507d8884
5 changed files with 79 additions and 3 deletions
|
|
@ -13,7 +13,7 @@ import { BASE, capture, jsonOut, project, restore } from './helpers/store-mock';
|
|||
afterEach(restore);
|
||||
|
||||
const NAMED: ShipConfig = { ...BASE, app: { ...BASE.app, name: 'STAR DASH' }, apple: { ...BASE.apple!, name: 'STAR DASH: Stone Age to 2100', primaryLocale: 'en-US' } };
|
||||
const PLAIN: ShipConfig = { ...BASE, app: { ...BASE.app, name: 'STAR DASH' }, apple: { ...BASE.apple!, primaryLocale: 'en-US' } };
|
||||
const PLAIN: ShipConfig = { ...BASE, app: { ...BASE.app, name: 'STAR DASH', copyright: '2026 Example Brand' }, apple: { ...BASE.apple!, primaryLocale: 'en-US' } };
|
||||
const sec = (loc: string, name?: string) => `## ${loc}\n\n${name ? `### name\n\`\`\`\n${name}\n\`\`\`\n\n` : ''}### subtitle\n\`\`\`\nSub\n\`\`\`\n\n### promotionalText\n\`\`\`\nPromo\n\`\`\`\n\n### keywords\n\`\`\`\nrun,time\n\`\`\`\n\n### description\n\`\`\`\nText.\n\`\`\`\n\n`;
|
||||
|
||||
describe('apple.name', () => {
|
||||
|
|
|
|||
59
tests/copyright-config-edit.test.ts
Normal file
59
tests/copyright-config-edit.test.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// app.copyright: check blocks when an apple app has none, validate reads the live version's copyright; and every
|
||||
// config writer (config-edit.ts) edits storebound.config.ts in place, keeping unknown keys and comments
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import { removeKeyPath, upsertBlock, upsertNestedString } from '../cli/lib/scan/config-edit';
|
||||
import { COPYRIGHT_MISSING, copyrightRules } from '../cli/lib/validate-rules';
|
||||
import { run as check } from '../cli/check';
|
||||
import { BASE, capture, project } from './helpers/store-mock';
|
||||
|
||||
describe('app.copyright', () => {
|
||||
test('check reports it missing for an apple app, never fills it', async () => {
|
||||
const r = await capture(() => check(project(BASE).ctx(['--json'], ['json'])));
|
||||
expect(r.out + r.err).toContain(COPYRIGHT_MISSING);
|
||||
const ok = await capture(() => check(project({ ...BASE, app: { ...BASE.app, copyright: '2026 Example Brand' } }).ctx(['--json'], ['json'])));
|
||||
expect(ok.out + ok.err).not.toContain('app.copyright is missing');
|
||||
const playOnly = await capture(() => check(project({ ...BASE, apple: undefined }).ctx(['--json'], ['json'])));
|
||||
expect(playOnly.out + playOnly.err).not.toContain('app.copyright is missing');
|
||||
});
|
||||
test('validate: says whether the store has one', () => {
|
||||
const v = (copyright: string | null) => ({ versionString: '1.0', state: 'PREPARE_FOR_SUBMISSION', copyright });
|
||||
expect(copyrightRules({ version: v(null) })[0]).toMatchObject({ level: 'blocker', code: 'copyright' });
|
||||
expect(copyrightRules({ version: v(null) })[0]!.msg).toContain(COPYRIGHT_MISSING);
|
||||
expect(copyrightRules({ version: v(null), configCopyright: '2026 B' })[0]!.msg).toContain("config has '2026 B'");
|
||||
expect(copyrightRules({ version: v('2026 B'), configCopyright: '2026 B' })[0]).toMatchObject({ level: 'ok', msg: 'copyright on the store: 2026 B' });
|
||||
expect(copyrightRules({ version: v('2026 B') })[0]!.level).toBe('warn');
|
||||
expect(copyrightRules({ version: null })).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('config writers round-trip', () => {
|
||||
const SRC = `// my project notes: keep me
|
||||
import { defineConfig } from 'storebound';
|
||||
|
||||
export default defineConfig({
|
||||
app: {
|
||||
name: 'Example', // inline comment
|
||||
customThing: { nested: [1, 2, 3] }, // unknown key
|
||||
},
|
||||
/* block comment */
|
||||
futureKey: 'kept',
|
||||
play: { packageName: 'com.example.app' },
|
||||
accounts: { enabled: true },
|
||||
});
|
||||
`;
|
||||
test('upsertBlock, upsertNestedString and removeKeyPath keep everything else byte for byte', () => {
|
||||
let t = upsertNestedString(SRC, 'c.ts', 'app', 'copyright', '2026 Example');
|
||||
t = upsertNestedString(t, 'c.ts', 'play', 'appId', '123');
|
||||
t = upsertBlock(t, 'c.ts', 'dataSafety', { collectsData: false }, 'note');
|
||||
for (const keep of ['// my project notes: keep me', '// inline comment', "customThing: { nested: [1, 2, 3] }, // unknown key", '/* block comment */', "futureKey: 'kept'"]) expect(t).toContain(keep);
|
||||
expect(t).toContain("copyright: '2026 Example'");
|
||||
// writing the same value again changes nothing; removing what was added gets the original back
|
||||
expect(upsertNestedString(t, 'c.ts', 'app', 'copyright', '2026 Example')).toBe(t);
|
||||
const back = removeKeyPath(removeKeyPath(removeKeyPath(t, 'c.ts', ['dataSafety']), 'c.ts', ['play', 'appId']), 'c.ts', ['app', 'copyright']);
|
||||
// a removed last key may leave a trailing comma behind (valid TS)
|
||||
expect(back.replace(/,(\s*})/g, '$1').replace(/\s+/g, ' ')).toBe(SRC.replace(/,(\s*})/g, '$1').replace(/\s+/g, ' '));
|
||||
// json configs keep unknown keys too
|
||||
const j = JSON.parse(upsertNestedString(JSON.stringify({ app: { name: 'x', extra: 1 }, other: [1] }), 'c.json', 'app', 'copyright', '2026 X'));
|
||||
expect(j).toEqual({ app: { name: 'x', extra: 1, copyright: '2026 X' }, other: [1] });
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue