// @requirements REQ-PRODUCT-SERVICE-CATALOG REQ-PRODUCT-A11Y REQ-PRODUCT-MOBILE REQ-PRODUCT-SAFE-CONTENT
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {JSDOM} from 'jsdom';
import {createElement} from 'react';
import {renderToStaticMarkup} from 'react-dom/server';
import {build} from 'esbuild';
import {createServiceInventory,createServiceDirectory,renderServiceDirectory,renderServiceDirectoryPage,mountServiceDirectory} from 'notabenda/services';
import {ServiceDirectory} from 'notabenda/react/documents';
import {createLegalDocument} from 'notabenda';
import {compileLegalDocuments} from 'notabenda/content';
import {fixture} from './helpers.mjs';
import {serviceExampleConfig} from '../../../examples/services/build.mjs';
const settings={idPrefix:'test-services'};
const config=async()=>serviceExampleConfig(await fixture({reviewed:true}));
test('directory and privacy use detached declared service facts, including custom services, with selected-only public data',async()=>{
const c=await config();c.services[0].resourceOrigins=['https://internal-scope.example.test'];c.private={note:'PRIVATE_SENTINEL'};
const model=createServiceDirectory(c);assert.equal(model.services.length,4);assert.equal(model.totalServices,4);assert.equal(model.policies.privacy.version,c.documents.privacy.version);
const inventory=createServiceInventory(c),privacy=createLegalDocument(c,'privacy').sections.find(s=>s.id==='providers');for(const service of inventory){assert.ok(privacy.items.some(item=>item.includes(service.name)&&item.includes(service.provider)&&item.includes(service.location)));}
assert.doesNotMatch(JSON.stringify(model),/PRIVATE_SENTINEL|internal-scope|resourceOrigins|scopeGuards|googleBasic|catalogRevision/);
model.services[0].purposes[0].dataCategories.push('Changed');model.services[0].provider='Changed';assert.notEqual(c.services[0].provider,'Changed');assert.ok(!c.purposes[0].dataCategories.includes('Changed'));
const pinned=compileLegalDocuments(c).artifact,old=renderServiceDirectory(pinned.publicConfig,settings);c.services[0].name='Later inventory revision';assert.equal(renderServiceDirectory(pinned.publicConfig,settings),old);assert.notEqual(renderServiceDirectory(c,settings),old);
});
test('directory filters are literal bounded searches with declared-purpose validation and clear partial counts',async()=>{
const c=await config();assert.equal(createServiceDirectory(c,{query:'brookpost'}).services[0].id,'mail');assert.equal(createServiceDirectory(c,{query:'IP address'}).services[0].id,'hosting');assert.equal(createServiceDirectory(c,{query:'readership_id',purposeId:'measurement'}).services[0].id,'measurement');assert.equal(createServiceDirectory(c,{query:'(a+)+'}).services.length,0);
assert.match(renderServiceDirectory(c,{...settings,purposeId:'measurement'}),/Showing 1 of 4 declared services/);
for(const bad of [{query:null},{query:'a'.repeat(121)},{query:'\u001b[2J'},{purposeId:'unknown'},{purposeId:['measurement']}])assert.throws(()=>createServiceDirectory(c,bad));
});
test('native directory is script-free, escaped, keyboard-readable and makes missing facts explicit',async()=>{
const c=await config();c.services[0].name='';c.services[0].provider='Provider ';
const html=renderServiceDirectory(c,{...settings,headingLevel:2});const dom=new JSDOM(html),doc=dom.window.document;
assert.equal(doc.querySelectorAll('article').length,4);assert.equal(doc.querySelectorAll('details > summary').length,4);assert.equal(doc.querySelectorAll('input,select,button,script,img').length,0);assert.equal(doc.querySelector('article h3').textContent,c.services[0].name);assert.equal(doc.querySelectorAll('details section h4').length,8);
assert.match(html,/A provider privacy link is not declared/);assert.match(html,/No browser storage is declared for this service/);assert.match(html,/<script>/);for(const node of doc.querySelectorAll('[aria-labelledby]'))assert.ok(doc.getElementById(node.getAttribute('aria-labelledby')));
assert.equal(new Set([...doc.querySelectorAll('[id]')].map(n=>n.id)).size,doc.querySelectorAll('[id]').length);
for(const bad of [{idPrefix:'bad id'},{...settings,headingLevel:6},{...settings,title:'\u202eunsafe'},{...settings,description:'x'.repeat(1001)}])assert.throws(()=>renderServiceDirectory(c,bad));
c.services[0].privacyUrl='javascript:alert(1)';assert.throws(()=>renderServiceDirectory(c,settings));dom.window.close();
});
test('production review, empty inventories, page link validation and exact React server parity are enforced',async()=>{
const c=await config();assert.equal(renderToStaticMarkup(createElement(ServiceDirectory,{config:c,...settings,className:'host'})),`