console: Play App content card Saves are the agent's (declaration-save; Send changes for review, Submit, Publish, Restart review stay forbidden), one owner batch per plan with the IARC Terms tick before the run, console plan play-app-content --all, New App fields typed not set, play.appId from the Create app redirect (console verify --url --write) and storebound play app-id across app list pages
This commit is contained in:
parent
1366e7e231
commit
f741c15829
29 changed files with 1029 additions and 165 deletions
|
|
@ -4,7 +4,7 @@
|
|||
import { describe, expect, test } from 'bun:test';
|
||||
import { IARC_QUESTIONS, type ShipConfig, validateConfig, validateExtras } from '../cli/lib/config';
|
||||
import { consoleAnswers, PLAN_FORM_IDS, PLAN_ONLY_FORM_IDS } from '../cli/lib/console-answers';
|
||||
import { formDef, forbiddenTick } from '../cli/lib/console-forms';
|
||||
import { declarationSaveAllowed, flowAllowed, forbiddenControl, formDef, forbiddenTick, publishingControl } from '../cli/lib/console-forms';
|
||||
import { consolePlan, IN_PAGE_LOOP, playConsoleReminders, planText } from '../cli/lib/console-plan';
|
||||
import { APP_CONTENT_CARDS, PLAY_MATERIAL_FACT, startLabel } from '../cli/lib/console-play-plans';
|
||||
import { appPrivacyPublished, verifyAppContent, verifyPlayDeclaration, verifyText } from '../cli/lib/console-verify';
|
||||
|
|
@ -44,7 +44,19 @@ describe('agent-only Play plans: shape, URLs, never-press lists, the owner batch
|
|||
expect(p.askOwner).toHaveLength(p.answers.filter(a => a.status !== 'certain').length);
|
||||
for (const b of ['Publish', 'Submit', 'Send changes for review']) expect(p.buttons.neverPress).toContain(b);
|
||||
for (const b of p.buttons.allowed) expect(b.class).not.toBe('final-submit');
|
||||
if (form.startsWith('play-') && !['play-store-settings', 'play-app-content', 'play-production-countries', 'play-production-release'].includes(form)) { expect(p.buttons.final).toBe('Save'); expect(p.buttons.neverPress).toContain('Save'); }
|
||||
// an App content card's Save is the agent's (verified 2026-10-13); the owner's button is Send changes for review
|
||||
if (form.startsWith('play-') && !['play-store-settings', 'play-app-content', 'play-production-countries', 'play-production-release'].includes(form)) {
|
||||
expect(p.buttons.final).toBe('Send changes for review');
|
||||
expect(p.buttons.neverPress).not.toContain('Save');
|
||||
expect(p.buttons.allowed).toContainEqual(expect.objectContaining({ button: 'Save', class: 'declaration-save' }));
|
||||
expect(p.steps.some(s => s.text.startsWith('Press Save yourself'))).toBe(true);
|
||||
const last = p.ownerBatch.at(-1)!;
|
||||
expect([last.when, last.kind]).toEqual(['after', 'final']);
|
||||
expect(last.text).toContain('Send changes for review');
|
||||
}
|
||||
for (const b of ['Restart review', 'Submit N changes for review']) expect(p.buttons.neverPress).toContain(b);
|
||||
p.ownerBatch.forEach((o, i) => expect(o.n).toBe(i + 1));
|
||||
expect(planText(p).indexOf('Owner batch')).toBeLessThan(planText(p).indexOf('Steps:'));
|
||||
expect(p.uiFacts.join('\n')).toContain(PLAY_MATERIAL_FACT);
|
||||
expect(p.verify.command).toBe(`storebound console verify ${form} --from-text -`);
|
||||
expect(p.inPageLoop).toBe(IN_PAGE_LOOP);
|
||||
|
|
@ -89,18 +101,96 @@ describe('agent-only Play plans: shape, URLs, never-press lists, the owner batch
|
|||
expect(p.answers.some(a => a.id === 'play-content-rating/agree:iarcTerms' && a.status === 'agreement')).toBe(true);
|
||||
expect(p.askOwner.some(l => l.startsWith('Content ratings: IARC Terms of Use: an agreement'))).toBe(true);
|
||||
expect(p.askOwner.some(l => l.startsWith('Government apps: '))).toBe(true);
|
||||
for (const b of ['IARC Terms of Use box', 'Save (Summary page)', 'Save', 'Submit', 'Create app']) expect(p.buttons.neverPress).toContain(b);
|
||||
for (const b of ['IARC Terms of Use box', 'Send changes for review', 'Restart review', 'Submit', 'Create app']) expect(p.buttons.neverPress).toContain(b);
|
||||
for (const b of ['Save', 'Save (Summary page)']) expect(p.buttons.neverPress).not.toContain(b);
|
||||
expect(p.buttons.final).toBe('Send changes for review');
|
||||
expect(p.verify.capture).toContain('[aria-label^="Start "]');
|
||||
});
|
||||
|
||||
test('App content: ONE owner batch at the top: the open answers, the IARC Terms tick before the run (with its place), Send changes for review after', () => {
|
||||
const p = consolePlan('play-app-content', { ...base, console: { ...base.console, appContent: undefined } } as ShipConfig);
|
||||
const kinds = p.ownerBatch.map(o => `${o.when}:${o.kind}`);
|
||||
expect(kinds).toEqual(['before:answers', 'before:agreement', 'after:final']);
|
||||
expect(p.ownerBatch[0]!.text).toContain('Is the app developed by or on behalf of a government?');
|
||||
expect(p.ownerBatch[1]).toMatchObject({ text: 'Tick the IARC Terms of Use box' });
|
||||
expect(p.ownerBatch[1]!.where).toContain(`${dev}/app-content/content-rating-iarc-questionnaire`);
|
||||
expect(p.ownerBatch[1]!.where).toContain('page 1');
|
||||
expect(p.ownerBatch[2]!.text).toContain('Send changes for review');
|
||||
const text = planText(p);
|
||||
expect(text.indexOf('Owner batch')).toBeLessThan(text.indexOf('start:'));
|
||||
expect(text).toContain('[before the run] Tick the IARC Terms of Use box');
|
||||
expect(p.steps[0]!.text).toContain('ONE message');
|
||||
// a complete config: only the two owner steps are left
|
||||
const done = consolePlan('play-app-content', { ...base, console: { ...base.console, appContent: { government: false, financialFeatures: false, healthFeatures: false, advertisingId: true } }, reviewAccess: { signInRequired: false } } as ShipConfig);
|
||||
const steps = done.ownerBatch.filter(o => o.kind !== 'answers');
|
||||
expect(steps.map(o => [o.when, o.kind])).toEqual([['before', 'agreement'], ['after', 'final']]);
|
||||
expect(steps[0]!.text).toBe('Tick the IARC Terms of Use box');
|
||||
expect(steps[1]!.text).toContain('Send changes for review');
|
||||
});
|
||||
|
||||
test('content rating: the owner batch has the IARC Terms tick before the run; the Summary Save is the agent\'s', () => {
|
||||
const p = consolePlan('play-content-rating', base);
|
||||
expect(p.ownerBatch.map(o => [o.when, o.kind])).toEqual([['before', 'answers'], ['before', 'agreement'], ['after', 'final']]);
|
||||
expect(p.ownerBatch[1]!.where).toContain('Next does nothing until it is ticked');
|
||||
expect(p.steps.map(s => s.text).join('\n')).toContain('Next does nothing (verified 2026-10-13)');
|
||||
expect(p.uiFacts.join('\n')).toContain('page 1\'s Next does nothing until the IARC Terms of Use box is ticked');
|
||||
});
|
||||
|
||||
test('--all: every card inline in fix order, ending with the one owner list [IARC Terms (before), Send changes for review (after)]', () => {
|
||||
const cfg = { ...base, console: { ...base.console, appContent: { government: false, financialFeatures: false, healthFeatures: false, advertisingId: true } }, reviewAccess: { signInRequired: false } } as ShipConfig;
|
||||
const one = consolePlan('play-app-content', cfg), all = consolePlan('play-app-content', cfg, {}, { all: true });
|
||||
expect(all.steps.length).toBeGreaterThan(one.steps.length + 20);
|
||||
all.steps.forEach((s, i) => expect(s.n).toBe(i + 1));
|
||||
// the cards in the overview's order
|
||||
const at = APP_CONTENT_CARDS.map(c => all.steps.findIndex(s => s.text.startsWith(`${c.name}: `)));
|
||||
expect(at.every(i => i > 0)).toBe(true);
|
||||
expect([...at].sort((a, b) => a - b)).toEqual(at);
|
||||
// every answer of every card is set in a step; answer ids keep their card
|
||||
const inSteps = new Set(all.steps.flatMap(s => s.answers ?? []));
|
||||
for (const a of all.answers) expect({ id: a.id, listed: inSteps.has(a.id) }).toEqual({ id: a.id, listed: true });
|
||||
// card Saves are pressed by the agent, the run ends with the owner list
|
||||
expect(all.steps.filter(s => s.text.includes('Press Save yourself')).length).toBeGreaterThanOrEqual(7);
|
||||
const last = all.steps.at(-1)!.text;
|
||||
expect(last).toContain('[before: Tick the IARC Terms of Use box]');
|
||||
expect(last).toContain('[after: Press Send changes for review');
|
||||
expect(all.ownerBatch.filter(o => o.kind !== 'answers').map(o => o.when)).toEqual(['before', 'after']);
|
||||
expect(all.buttons.final).toBe('Send changes for review');
|
||||
});
|
||||
|
||||
test('the click gate: card Saves allowed on an App content card page; Send changes for review, Submit, Publish, Restart review never', () => {
|
||||
const card = (slug: string) => ({ url: `${dev}/app-content/${slug}` });
|
||||
for (const slug of ['privacy-policy', 'ads-declaration', 'testing-credentials', 'ad-id-declaration', 'government-apps', 'finance', 'health', 'target-audience-content', 'content-rating-iarc-questionnaire', 'data-privacy-security'])
|
||||
expect({ slug, ok: declarationSaveAllowed('Save', 'button', card(slug)) }).toEqual({ slug, ok: true });
|
||||
expect(declarationSaveAllowed('Kaydet', 'button', card('ads-declaration'))).toBe(true);
|
||||
for (const n of ['Send changes for review', 'Submit', 'Publish', 'Restart review', 'Submit 3 changes for review', 'Save and publish', 'Save draft', 'Start rollout'])
|
||||
expect({ n, ok: declarationSaveAllowed(n, 'button', card('ads-declaration')) }).toEqual({ n, ok: false });
|
||||
// only on a card page, only a button
|
||||
expect(declarationSaveAllowed('Save', 'button', { url: `${dev}/app-content/overview` })).toBe(false);
|
||||
expect(declarationSaveAllowed('Save', 'button', { url: `${dev}/tracks/production` })).toBe(false);
|
||||
expect(declarationSaveAllowed('Save', 'button', { url: 'https://example.com/app-content/ads-declaration' })).toBe(false);
|
||||
expect(declarationSaveAllowed('Save', 'link', card('ads-declaration'))).toBe(false);
|
||||
for (const n of ['Send changes for review', 'Submit', 'Publish', 'Restart review', 'Submit 3 changes for review', 'Değişiklikleri incelemeye gönder']) {
|
||||
expect(publishingControl(n)).toBe(true);
|
||||
expect(forbiddenControl(n)).toBe(true);
|
||||
for (const f of ['play-content-rating', 'play-target-audience'] as const) expect(flowAllowed(f, n, 'button', { privacyPage: false, inDialog: false, inDataTypes: false, dataTypes: [] })).toBe(false);
|
||||
}
|
||||
for (const n of ['Save', 'Releases', 'Next']) expect(publishingControl(n)).toBe(false);
|
||||
});
|
||||
|
||||
test('Sign in details: the demo account never printed, typed by the owner', () => {
|
||||
const p = consolePlan('play-sign-in-details', { ...base, reviewAccess: { signInRequired: true, instructions: 'Sign in with the demo account.' } } as ShipConfig, { demoAccount: true });
|
||||
expect(p.answers.find(a => a.id === 'access')).toMatchObject({ value: true, status: 'certain', label: 'All or some functionality in my app is restricted' });
|
||||
expect(p.answers.find(a => a.id === 'instructions')).toMatchObject({ status: 'certain', action: 'type' });
|
||||
const cred = p.answers.find(a => a.id === 'credentials')!;
|
||||
expect(cred).toMatchObject({ status: 'ask-owner', value: null });
|
||||
// in .storebound/credentials.json: the agent types it, never printed (no value in the plan)
|
||||
expect(cred).toMatchObject({ status: 'certain', value: null, action: 'type', text: 'from .storebound/credentials.json (never printed)' });
|
||||
expect(cred.note).toContain('reviewDemoAccount: present');
|
||||
expect(cred.note).toContain('never types a password');
|
||||
expect(p.ownerBatch.map(o => o.kind)).toEqual(['final']);
|
||||
// kept out of .storebound: the owner types it (in the owner batch, before the run)
|
||||
const own = consolePlan('play-sign-in-details', { ...base, reviewAccess: { signInRequired: true, instructions: 'Sign in with the demo account.' } } as ShipConfig, { demoAccount: false });
|
||||
expect(own.answers.find(a => a.id === 'credentials')).toMatchObject({ status: 'ask-owner', value: null });
|
||||
expect(own.ownerBatch.map(o => [o.when, o.kind])).toEqual([['before', 'credentials'], ['after', 'final']]);
|
||||
expect(JSON.stringify(own)).not.toMatch(/password"\s*:/i);
|
||||
expect(consolePlan('play-sign-in-details', base).answers.map(a => a.status)).toEqual(['ask-owner']);
|
||||
});
|
||||
|
||||
|
|
@ -117,12 +207,14 @@ describe('agent-only Play plans: shape, URLs, never-press lists, the owner batch
|
|||
});
|
||||
|
||||
describe('IARC content rating: verified flow, follow-ups, never-press', () => {
|
||||
test('the IARC Terms box is an agreement, the Summary\'s Save is final (not Submit), the flow and example ratings are recorded', () => {
|
||||
test('the IARC Terms box is an agreement, the Summary\'s Save (not Submit) is the agent\'s declaration save, the flow and example ratings are recorded', () => {
|
||||
const p = consolePlan('play-content-rating', base);
|
||||
expect(p.answers.find(a => a.id === 'agree:iarcTerms')).toMatchObject({ status: 'agreement', value: null, action: 'untick' });
|
||||
expect(p.buttons.final).toBe('Save (Summary page)');
|
||||
for (const b of ['IARC Terms of Use box', 'Save (Summary page)', 'Submit']) expect(p.buttons.neverPress).toContain(b);
|
||||
expect(p.buttons.final).toBe('Send changes for review');
|
||||
for (const b of ['IARC Terms of Use box', 'Send changes for review', 'Submit']) expect(p.buttons.neverPress).toContain(b);
|
||||
expect(p.buttons.neverPress).not.toContain('Save (Summary page)');
|
||||
expect(p.buttons.allowed.map(b => b.button)).toContain('Save draft / Save (questionnaire page)');
|
||||
expect(p.buttons.allowed).toContainEqual(expect.objectContaining({ button: 'Save (Summary page)', class: 'declaration-save' }));
|
||||
const facts = p.uiFacts.join('\n');
|
||||
for (const f of ['content-rating-iarc-questionnaire', 'one long page', '"Save" (not "Submit")', 'PEGI 7, ESRB E10+, USK 12, ClassInd 18', 'Google Play 7+']) expect(facts).toContain(f);
|
||||
expect(p.askOwner.some(l => l.startsWith('IARC Terms of Use: an agreement'))).toBe(true);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue