// @requirements REQ-PRODUCT-SCOPE-GUARDS
// @requirements REQ-PRODUCT-PROCESSING-CHOICES
// @requirements REQ-PRODUCT-REACT REQ-PRODUCT-CONSENT-RUNTIME REQ-PRODUCT-CONSENT-EVIDENCE REQ-PRODUCT-SAFE-CONTENT
import {test as nodeTest} from 'node:test';
import assert from 'node:assert/strict';
import {createElement as h,StrictMode,act} from 'react';
import {renderToString,renderToStaticMarkup} from 'react-dom/server';
import {createRoot,hydrateRoot} from 'react-dom/client';
import {JSDOM} from 'jsdom';
import {createProcessingChoices,renderLegalDocument,renderLegalLinks} from 'notabenda';
import * as compatibilityReact from 'notabenda/react';
import * as compiledReact from 'notabenda/react/consent';
import {consentProfiles} from './consent-profiles.mjs';
import {LegalDocument,LegalLinks,CollectionNotice} from 'notabenda/react/documents';
import {compileLegalDocuments,createConsentNotice,createProcessingNotice,createCollectionNotice} from 'notabenda/content';
import {recordLegalRelease,registerConsentNotice,setActiveConsentNotice,recordConsentChoice,getConsentHead} from 'notabenda-cf';
import {createSyntheticStorage} from '../../notabenda-cf/examples/synthetic.mjs';
import {fixture} from './helpers.mjs';
import {App} from '../../../examples/react/view.mjs';
const instant='2026-10-18T12:00:00.000Z';
async function createSetup(createConsent,id='react-example') {
const config=await fixture({reviewed:true});config.site.id=id;
config.services=[{id:'optional-sample',name:'Fictional optional service',provider:'Fictional operator',location:'Local test',purposeIds:config.purposes.map(p=>p.id)}];config.storage=[];
config.consent={purposes:config.purposes.map(p=>({id:p.id,scope:'one'})),services:config.services.map(s=>({id:s.id,scope:'one'})),grantLifetimeDays:30,refusalLifetimeDays:180,dismissalMinutes:30};
const values=new Map();const storage={getItem:key=>values.get(key)??null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)};
const env={storage,now:()=>Date.parse(instant),schedule:()=>()=>{}};
return {config,values,env,purpose:config.consent.purposes[0].id,controller:createConsent(config,env)};
}
async function domTest(callback,html='') {
const dom=new JSDOM(`
${html}
`,{url:'https://fictional.example.test/'});
const keys=['window','document','HTMLElement','Node','Event','MouseEvent','IS_REACT_ACT_ENVIRONMENT'];const previous=new Map(keys.map(key=>[key,Object.getOwnPropertyDescriptor(globalThis,key)]));
for(const key of keys)Object.defineProperty(globalThis,key,{value:key==='IS_REACT_ACT_ENVIRONMENT'?true:dom.window[key],configurable:true,writable:true});
// jsdom has no native modal implementation. Real dialog/layout behavior is verified separately in Chromium.
dom.window.HTMLDialogElement.prototype.showModal=function(){this.open=true;};
dom.window.HTMLDialogElement.prototype.close=function(){this.open=false;this.dispatchEvent(new dom.window.Event('close'));};
try{await callback(dom.window.document.getElementById('root'),dom.window);}finally{dom.window.close();for(const [key,value] of previous)value?Object.defineProperty(globalThis,key,value):delete globalThis[key];}
}
for(const [index,[profile,createConsent]] of consentProfiles.entries()){
const test=(name,run)=>nodeTest(`${profile}: ${name}`,run);
const setup=id=>createSetup(createConsent,id);
const {ConsentProvider,ConsentControls,PreferencesButton,useConsentController,useConsentSnapshot,usePurposeAllowed,useServiceAllowed}=index===0?compatibilityReact:compiledReact;
const Probe=({purpose,onSnapshot})=>{const state=useConsentSnapshot();const allowed=usePurposeAllowed(purpose);onSnapshot?.(state);return h('output',{'data-permission':allowed?'allowed':'blocked'},state.purposes[purpose]??'unknown');};
// @requirements REQ-PRODUCT-PRIVACY-SIGNALS
test('React service permission remains distinct from cookie permission, with pure immutable signal snapshots and conservative SSR',async()=>{
const f=await setup();f.controller.destroy();f.config.services[0].processingActivities=['sharing'];f.config.privacySignals={globalPrivacyControl:{appliesTo:['sharing'],explanation:'Fictional sharing restriction.'}};
const controller=createConsent(f.config,{...f.env,globalPrivacyControl:()=>true});controller.acceptAll();
let captured;const SignalProbe=()=>{captured=useConsentSnapshot();return h('output',{'data-cookie':usePurposeAllowed(f.purpose),'data-service':useServiceAllowed('optional-sample')},captured.privacySignals?.globalPrivacyControl??'unknown');};
const tree=h(ConsentProvider,{controller},h(SignalProbe));const html=renderToString(tree);assert.match(html,/data-cookie="false" data-service="false"/);
controller.isServiceAllowed=()=>{throw new Error('React render cannot invoke effectful service checks.');};
await domTest(async container=>{const root=createRoot(container);await act(async()=>root.render(tree));assert.equal(container.querySelector('output').dataset.cookie,'true');assert.equal(container.querySelector('output').dataset.service,'false');assert.equal(captured.privacySignals.globalPrivacyControl,'observed');assert.throws(()=>captured.servicePermissions['optional-sample']=true,TypeError);assert.throws(()=>captured.privacySignals.restrictedServices['optional-sample'].push('sale'),TypeError);await act(async()=>root.unmount());});controller.destroy();
});
test('React processing state is deeply immutable, conservative during reads and never invokes effectful permission checks',async()=>{
const f=await setup();f.controller.destroy();f.config.services[0].processingActivities=['sale'];
const release=compileLegalDocuments(f.config),notice=createProcessingNotice(release.artifact,{scopeId:'privacy-choices',activities:['sale'],wording:{title:'Privacy choices',description:'Fictional opt-out.',actionLabel:'Opt out'}});
const processing=createProcessingChoices(notice,{streamRef:'a'.repeat(64),read:()=>new Promise(()=>{}),submit:async()=>{throw new Error('Unused');}},{storage:f.env.storage});
const controller=createConsent(f.config,f.env,{processingChoices:processing});controller.acceptAll();let captured;
controller.isServiceAllowed=()=>{throw new Error('Effectful check during render.');};
const Component=()=>{captured=useConsentSnapshot();return h('output',{'data-cookie':usePurposeAllowed(f.purpose),'data-service':useServiceAllowed('optional-sample')},captured.processingChoices?.status);};
const tree=h(ConsentProvider,{controller},h(Component));assert.match(renderToString(tree),/data-cookie="false" data-service="false"/);
await domTest(async container=>{const root=createRoot(container);await act(async()=>root.render(tree));assert.equal(container.querySelector('output').dataset.cookie,'true');assert.equal(container.querySelector('output').dataset.service,'false');assert.equal(captured.processingChoices.status,'checking');assert.throws(()=>captured.processingChoices.blockedActivities.pop(),TypeError);assert.throws(()=>captured.processingChoices.restrictedActivities.push('sale'),TypeError);assert.throws(()=>captured.processingChoices.revision=1,TypeError);await act(async()=>root.unmount());});
controller.destroy();processing.destroy();await processing.settled();
});
test('React document components preserve exact core escaping and work without a provider or client hooks',async()=>{
const f=await setup();f.config.documents.terms.sections[0].paragraphs=['Literal text.'];
assert.equal(renderToStaticMarkup(h(LegalDocument,{config:f.config,kind:'terms'})),`${renderLegalDocument(f.config,'terms')}
`);
assert.equal(renderToStaticMarkup(h(LegalLinks,{config:f.config})),`${renderLegalLinks(f.config)}
`);
assert.doesNotMatch(renderToStaticMarkup(h(LegalDocument,{config:f.config,kind:'terms'})),/