@@ -22,6 +22,7 @@ import type { GenContext } from "../../src/generator.js";
2222import { docsFile } from "../../src/generators/docs-file.js" ;
2323import { apiDocsFile } from "../../src/generators/api-docs-file.js" ;
2424import type { OutputLayout } from "../../src/import-path.js" ;
25+ import { apiSurfaceHref } from "../../src/docs-paths.js" ;
2526import { buildPkMap } from "../../src/pk-resolver.js" ;
2627import { buildRelationMap } from "../../src/relation-resolver.js" ;
2728import { makeRenderContext } from "../../src/render-context.js" ;
@@ -95,6 +96,21 @@ async function emitBoth(fixture: string, layout: OutputLayout): Promise<Emitted[
9596 return [ ...model , ...api ] ;
9697}
9798
99+ const TWO_SURFACES = [
100+ { label : "TypeScript" , subDir : "api/ts" } ,
101+ { label : "Java" , subDir : "api/java" } ,
102+ ] ;
103+ /** Emit the model + BOTH api surfaces (the 2nd simulates another language's
104+ * port by re-running the api engine under a second subDir). */
105+ async function emitMulti ( fixture : string , layout : OutputLayout ) : Promise < Emitted [ ] > {
106+ const { root, inputDir } = await loadFixture ( fixture ) ;
107+ const ctx = makeCtx ( root , inputDir , layout ) ;
108+ const model = ( await docsFile ( { apiSurfaces : TWO_SURFACES } ) . generate ( ctx ) ) as Emitted [ ] ;
109+ const tsApi = ( await apiDocsFile ( { subDir : "api/ts" , modelSurface : true } ) . generate ( ctx ) ) as Emitted [ ] ;
110+ const javaApi = ( await apiDocsFile ( { subDir : "api/java" , modelSurface : true } ) . generate ( ctx ) ) as Emitted [ ] ;
111+ return [ ...model , ...tsApi , ...javaApi ] ;
112+ }
113+
98114// ── Cross-link finder (pure — unit-testable for teeth) ───────────────────────
99115
100116const isApi = ( p : string ) : boolean => p . startsWith ( "api/" ) ;
@@ -187,3 +203,37 @@ describe("unified-docs-door cross-link integrity", () => {
187203 ] ) ;
188204 } ) ;
189205} ) ;
206+
207+ // ── Polyglot: model + TWO api surfaces (e.g. a TS + Java solution) ────────────
208+
209+ describe ( "polyglot multi-surface cross-links" , ( ) => {
210+ for ( const [ label , fixture , layout ] of [
211+ [ "flat" , FIXTURE , "flat" ] ,
212+ [ "package" , FIXTURE_PACKAGE , "package" ] ,
213+ ] as const ) {
214+ it ( `every cross-link resolves with 2 api surfaces (${ label } )` , async ( ) => {
215+ const files = await emitMulti ( fixture , layout ) ;
216+ const present = new Set ( files . map ( ( f ) => f . path ) ) ;
217+ expect ( findBrokenCrossLinks ( files , present ) , `broken (${ label } )` ) . toEqual ( [ ] ) ;
218+
219+ // a model ENTITY page links BOTH surfaces:
220+ const entity = files . find ( ( f ) => ! isApi ( f . path ) && f . path !== "README.md" && f . path . endsWith ( ".md" ) ) ! ;
221+ expect ( entity . content ) . toContain ( "api/ts/" ) ;
222+ expect ( entity . content ) . toContain ( "api/java/" ) ;
223+
224+ // both api surfaces are present + each links back to the model:
225+ expect ( [ ...present ] . some ( ( p ) => p . startsWith ( "api/ts/" ) ) ) . toBe ( true ) ;
226+ expect ( [ ...present ] . some ( ( p ) => p . startsWith ( "api/java/" ) ) ) . toBe ( true ) ;
227+ const apiTs = files . find ( ( f ) => f . path . startsWith ( "api/ts/" ) && f . path . endsWith ( ".md" ) && ! f . path . endsWith ( "README.md" ) && ! f . path . endsWith ( "AGENT-API.md" ) ) ! ;
228+ const apiJava = files . find ( ( f ) => f . path . startsWith ( "api/java/" ) && f . path . endsWith ( ".md" ) && ! f . path . endsWith ( "README.md" ) && ! f . path . endsWith ( "AGENT-API.md" ) ) ! ;
229+ expect ( apiTs . content ) . toMatch ( / M o d e l | m e t a d a t a / i) ;
230+ expect ( apiJava . content ) . toMatch ( / M o d e l | m e t a d a t a / i) ;
231+ } ) ;
232+ }
233+
234+ it ( "a baseUrl surface yields an absolute (federated) link" , ( ) => {
235+ const href = apiSurfaceHref ( "Order.md" , { subDir : "api/java" , baseUrl : "https://docs.example/java" } , "Order.md" ) ;
236+ expect ( href ) . toBe ( "https://docs.example/java/Order.md" ) ;
237+ expect ( href . startsWith ( "http" ) ) . toBe ( true ) ; // not in the local tree → not a broken local link
238+ } ) ;
239+ } ) ;
0 commit comments