@@ -559,6 +559,112 @@ async function createDocWithMarkedTocEntry(
559559 return { docPath : markedDoc , entryAddress } ;
560560}
561561
562+ const CONFORMANCE_IMAGE_DATA_URI =
563+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=' ;
564+
565+ type ImagePlacement = 'inline' | 'floating' ;
566+ type ImageFixture = {
567+ docPath : string ;
568+ imageId : string ;
569+ } ;
570+
571+ function pickImageId (
572+ items : Record < string , unknown > [ ] ,
573+ context : string ,
574+ placement ?: ImagePlacement ,
575+ ) : { imageId : string ; item : Record < string , unknown > } {
576+ const match =
577+ placement === undefined
578+ ? items [ 0 ]
579+ : ( items . find ( ( item ) => {
580+ const address = item . address ;
581+ if ( ! address || typeof address !== 'object' ) return false ;
582+ return ( address as Record < string , unknown > ) . placement === placement ;
583+ } ) ?? items [ 0 ] ) ;
584+
585+ if ( ! match ) {
586+ throw new Error ( `[${ context } ] No images available.` ) ;
587+ }
588+
589+ const imageId = match . sdImageId ;
590+ if ( typeof imageId !== 'string' || imageId . length === 0 ) {
591+ throw new Error ( `[${ context } ] Unable to resolve image id from list output.` ) ;
592+ }
593+
594+ return { imageId, item : match } ;
595+ }
596+
597+ async function resolveImageFixture (
598+ harness : ConformanceHarness ,
599+ stateDir : string ,
600+ docPath : string ,
601+ context : string ,
602+ placement ?: ImagePlacement ,
603+ ) : Promise < ImageFixture > {
604+ const listed = await harness . runCli ( [ ...commandTokens ( 'doc.images.list' ) , docPath , '--limit' , '20' ] , stateDir ) ;
605+ if ( listed . result . code !== 0 || listed . envelope . ok !== true ) {
606+ throw new Error ( `[${ context } ] Failed to list images.` ) ;
607+ }
608+
609+ const items = extractDiscoveryItems ( listed . envelope . data ) ;
610+ const { imageId } = pickImageId ( items , context , placement ) ;
611+ return { docPath, imageId } ;
612+ }
613+
614+ async function createInlineImageFixture (
615+ harness : ConformanceHarness ,
616+ stateDir : string ,
617+ label : string ,
618+ ) : Promise < ImageFixture > {
619+ const sourceDoc = await harness . copyFixtureDoc ( `${ label } -source` ) ;
620+ const outputDoc = harness . createOutputPath ( `${ label } -with-image` ) ;
621+ const created = await harness . runCli (
622+ [
623+ ...commandTokens ( 'doc.create.image' ) ,
624+ sourceDoc ,
625+ '--src' ,
626+ CONFORMANCE_IMAGE_DATA_URI ,
627+ '--alt' ,
628+ 'Conformance image' ,
629+ '--at-json' ,
630+ JSON . stringify ( { kind : 'documentEnd' } ) ,
631+ '--out' ,
632+ outputDoc ,
633+ ] ,
634+ stateDir ,
635+ ) ;
636+ if ( created . result . code !== 0 || created . envelope . ok !== true ) {
637+ throw new Error ( `[${ label } ] Failed to create image fixture.` ) ;
638+ }
639+
640+ return resolveImageFixture ( harness , stateDir , outputDoc , `${ label } :inline` , 'inline' ) ;
641+ }
642+
643+ async function createFloatingImageFixture (
644+ harness : ConformanceHarness ,
645+ stateDir : string ,
646+ label : string ,
647+ ) : Promise < ImageFixture > {
648+ const inlineFixture = await createInlineImageFixture ( harness , stateDir , `${ label } -seed-inline` ) ;
649+ const floatingDoc = harness . createOutputPath ( `${ label } -floating` ) ;
650+ const converted = await harness . runCli (
651+ [
652+ ...commandTokens ( 'doc.images.convertToFloating' ) ,
653+ inlineFixture . docPath ,
654+ '--image-id' ,
655+ inlineFixture . imageId ,
656+ '--out' ,
657+ floatingDoc ,
658+ ] ,
659+ stateDir ,
660+ ) ;
661+ if ( converted . result . code !== 0 || converted . envelope . ok !== true ) {
662+ throw new Error ( `[${ label } ] Failed to convert fixture image to floating.` ) ;
663+ }
664+
665+ return resolveImageFixture ( harness , stateDir , floatingDoc , `${ label } :floating` , 'floating' ) ;
666+ }
667+
562668export const SUCCESS_SCENARIOS = {
563669 'doc.open' : async ( harness : ConformanceHarness ) : Promise < ScenarioInvocation > => {
564670 const stateDir = await harness . createStateDir ( 'doc-open-success' ) ;
@@ -1642,6 +1748,227 @@ export const SUCCESS_SCENARIOS = {
16421748 ] ,
16431749 } ;
16441750 } ,
1751+
1752+ // ---------------------------------------------------------------------------
1753+ // Image operations
1754+ // ---------------------------------------------------------------------------
1755+
1756+ 'doc.create.image' : async ( harness : ConformanceHarness ) : Promise < ScenarioInvocation > => {
1757+ const stateDir = await harness . createStateDir ( 'doc-create-image-success' ) ;
1758+ const docPath = await harness . copyFixtureDoc ( 'doc-create-image' ) ;
1759+ return {
1760+ stateDir,
1761+ args : [
1762+ ...commandTokens ( 'doc.create.image' ) ,
1763+ docPath ,
1764+ '--src' ,
1765+ CONFORMANCE_IMAGE_DATA_URI ,
1766+ '--alt' ,
1767+ 'Conformance image' ,
1768+ '--at-json' ,
1769+ JSON . stringify ( { kind : 'documentEnd' } ) ,
1770+ '--out' ,
1771+ harness . createOutputPath ( 'doc-create-image-output' ) ,
1772+ ] ,
1773+ } ;
1774+ } ,
1775+ 'doc.images.list' : async ( harness : ConformanceHarness ) : Promise < ScenarioInvocation > => {
1776+ const stateDir = await harness . createStateDir ( 'doc-images-list-success' ) ;
1777+ const fixture = await createInlineImageFixture ( harness , stateDir , 'doc-images-list' ) ;
1778+ return {
1779+ stateDir,
1780+ args : [ ...commandTokens ( 'doc.images.list' ) , fixture . docPath , '--limit' , '20' ] ,
1781+ } ;
1782+ } ,
1783+ 'doc.images.get' : async ( harness : ConformanceHarness ) : Promise < ScenarioInvocation > => {
1784+ const stateDir = await harness . createStateDir ( 'doc-images-get-success' ) ;
1785+ const fixture = await createInlineImageFixture ( harness , stateDir , 'doc-images-get' ) ;
1786+ return {
1787+ stateDir,
1788+ args : [ ...commandTokens ( 'doc.images.get' ) , fixture . docPath , '--image-id' , fixture . imageId ] ,
1789+ } ;
1790+ } ,
1791+ 'doc.images.delete' : async ( harness : ConformanceHarness ) : Promise < ScenarioInvocation > => {
1792+ const stateDir = await harness . createStateDir ( 'doc-images-delete-success' ) ;
1793+ const fixture = await createInlineImageFixture ( harness , stateDir , 'doc-images-delete' ) ;
1794+ return {
1795+ stateDir,
1796+ args : [
1797+ ...commandTokens ( 'doc.images.delete' ) ,
1798+ fixture . docPath ,
1799+ '--image-id' ,
1800+ fixture . imageId ,
1801+ '--out' ,
1802+ harness . createOutputPath ( 'doc-images-delete-output' ) ,
1803+ ] ,
1804+ } ;
1805+ } ,
1806+ 'doc.images.move' : async ( harness : ConformanceHarness ) : Promise < ScenarioInvocation > => {
1807+ const stateDir = await harness . createStateDir ( 'doc-images-move-success' ) ;
1808+ const fixture = await createInlineImageFixture ( harness , stateDir , 'doc-images-move' ) ;
1809+ return {
1810+ stateDir,
1811+ args : [
1812+ ...commandTokens ( 'doc.images.move' ) ,
1813+ fixture . docPath ,
1814+ '--image-id' ,
1815+ fixture . imageId ,
1816+ '--to-json' ,
1817+ JSON . stringify ( { kind : 'documentStart' } ) ,
1818+ '--out' ,
1819+ harness . createOutputPath ( 'doc-images-move-output' ) ,
1820+ ] ,
1821+ } ;
1822+ } ,
1823+ 'doc.images.convertToInline' : async ( harness : ConformanceHarness ) : Promise < ScenarioInvocation > => {
1824+ const stateDir = await harness . createStateDir ( 'doc-images-convert-to-inline-success' ) ;
1825+ const fixture = await createFloatingImageFixture ( harness , stateDir , 'doc-images-convert-to-inline' ) ;
1826+ return {
1827+ stateDir,
1828+ args : [
1829+ ...commandTokens ( 'doc.images.convertToInline' ) ,
1830+ fixture . docPath ,
1831+ '--image-id' ,
1832+ fixture . imageId ,
1833+ '--out' ,
1834+ harness . createOutputPath ( 'doc-images-convert-to-inline-output' ) ,
1835+ ] ,
1836+ } ;
1837+ } ,
1838+ 'doc.images.convertToFloating' : async ( harness : ConformanceHarness ) : Promise < ScenarioInvocation > => {
1839+ const stateDir = await harness . createStateDir ( 'doc-images-convert-to-floating-success' ) ;
1840+ const fixture = await createInlineImageFixture ( harness , stateDir , 'doc-images-convert-to-floating' ) ;
1841+ return {
1842+ stateDir,
1843+ args : [
1844+ ...commandTokens ( 'doc.images.convertToFloating' ) ,
1845+ fixture . docPath ,
1846+ '--image-id' ,
1847+ fixture . imageId ,
1848+ '--out' ,
1849+ harness . createOutputPath ( 'doc-images-convert-to-floating-output' ) ,
1850+ ] ,
1851+ } ;
1852+ } ,
1853+ 'doc.images.setSize' : async ( harness : ConformanceHarness ) : Promise < ScenarioInvocation > => {
1854+ const stateDir = await harness . createStateDir ( 'doc-images-set-size-success' ) ;
1855+ const fixture = await createInlineImageFixture ( harness , stateDir , 'doc-images-set-size' ) ;
1856+ return {
1857+ stateDir,
1858+ args : [
1859+ ...commandTokens ( 'doc.images.setSize' ) ,
1860+ fixture . docPath ,
1861+ '--image-id' ,
1862+ fixture . imageId ,
1863+ '--size-json' ,
1864+ JSON . stringify ( { width : 240 , height : 120 } ) ,
1865+ '--out' ,
1866+ harness . createOutputPath ( 'doc-images-set-size-output' ) ,
1867+ ] ,
1868+ } ;
1869+ } ,
1870+ 'doc.images.setWrapType' : async ( harness : ConformanceHarness ) : Promise < ScenarioInvocation > => {
1871+ const stateDir = await harness . createStateDir ( 'doc-images-set-wrap-type-success' ) ;
1872+ const fixture = await createFloatingImageFixture ( harness , stateDir , 'doc-images-set-wrap-type' ) ;
1873+ return {
1874+ stateDir,
1875+ args : [
1876+ ...commandTokens ( 'doc.images.setWrapType' ) ,
1877+ fixture . docPath ,
1878+ '--image-id' ,
1879+ fixture . imageId ,
1880+ '--type' ,
1881+ 'Tight' ,
1882+ '--out' ,
1883+ harness . createOutputPath ( 'doc-images-set-wrap-type-output' ) ,
1884+ ] ,
1885+ } ;
1886+ } ,
1887+ 'doc.images.setWrapSide' : async ( harness : ConformanceHarness ) : Promise < ScenarioInvocation > => {
1888+ const stateDir = await harness . createStateDir ( 'doc-images-set-wrap-side-success' ) ;
1889+ const fixture = await createFloatingImageFixture ( harness , stateDir , 'doc-images-set-wrap-side' ) ;
1890+ return {
1891+ stateDir,
1892+ args : [
1893+ ...commandTokens ( 'doc.images.setWrapSide' ) ,
1894+ fixture . docPath ,
1895+ '--image-id' ,
1896+ fixture . imageId ,
1897+ '--side' ,
1898+ 'left' ,
1899+ '--out' ,
1900+ harness . createOutputPath ( 'doc-images-set-wrap-side-output' ) ,
1901+ ] ,
1902+ } ;
1903+ } ,
1904+ 'doc.images.setWrapDistances' : async ( harness : ConformanceHarness ) : Promise < ScenarioInvocation > => {
1905+ const stateDir = await harness . createStateDir ( 'doc-images-set-wrap-distances-success' ) ;
1906+ const fixture = await createFloatingImageFixture ( harness , stateDir , 'doc-images-set-wrap-distances' ) ;
1907+ return {
1908+ stateDir,
1909+ args : [
1910+ ...commandTokens ( 'doc.images.setWrapDistances' ) ,
1911+ fixture . docPath ,
1912+ '--image-id' ,
1913+ fixture . imageId ,
1914+ '--distances-json' ,
1915+ JSON . stringify ( { distTop : 100 , distBottom : 100 } ) ,
1916+ '--out' ,
1917+ harness . createOutputPath ( 'doc-images-set-wrap-distances-output' ) ,
1918+ ] ,
1919+ } ;
1920+ } ,
1921+ 'doc.images.setPosition' : async ( harness : ConformanceHarness ) : Promise < ScenarioInvocation > => {
1922+ const stateDir = await harness . createStateDir ( 'doc-images-set-position-success' ) ;
1923+ const fixture = await createFloatingImageFixture ( harness , stateDir , 'doc-images-set-position' ) ;
1924+ return {
1925+ stateDir,
1926+ args : [
1927+ ...commandTokens ( 'doc.images.setPosition' ) ,
1928+ fixture . docPath ,
1929+ '--image-id' ,
1930+ fixture . imageId ,
1931+ '--position-json' ,
1932+ JSON . stringify ( { hRelativeFrom : 'column' , alignH : 'center' } ) ,
1933+ '--out' ,
1934+ harness . createOutputPath ( 'doc-images-set-position-output' ) ,
1935+ ] ,
1936+ } ;
1937+ } ,
1938+ 'doc.images.setAnchorOptions' : async ( harness : ConformanceHarness ) : Promise < ScenarioInvocation > => {
1939+ const stateDir = await harness . createStateDir ( 'doc-images-set-anchor-options-success' ) ;
1940+ const fixture = await createFloatingImageFixture ( harness , stateDir , 'doc-images-set-anchor-options' ) ;
1941+ return {
1942+ stateDir,
1943+ args : [
1944+ ...commandTokens ( 'doc.images.setAnchorOptions' ) ,
1945+ fixture . docPath ,
1946+ '--image-id' ,
1947+ fixture . imageId ,
1948+ '--options-json' ,
1949+ JSON . stringify ( { behindDoc : true , allowOverlap : false } ) ,
1950+ '--out' ,
1951+ harness . createOutputPath ( 'doc-images-set-anchor-options-output' ) ,
1952+ ] ,
1953+ } ;
1954+ } ,
1955+ 'doc.images.setZOrder' : async ( harness : ConformanceHarness ) : Promise < ScenarioInvocation > => {
1956+ const stateDir = await harness . createStateDir ( 'doc-images-set-z-order-success' ) ;
1957+ const fixture = await createFloatingImageFixture ( harness , stateDir , 'doc-images-set-z-order' ) ;
1958+ return {
1959+ stateDir,
1960+ args : [
1961+ ...commandTokens ( 'doc.images.setZOrder' ) ,
1962+ fixture . docPath ,
1963+ '--image-id' ,
1964+ fixture . imageId ,
1965+ '--z-order-json' ,
1966+ JSON . stringify ( { relativeHeight : 500 } ) ,
1967+ '--out' ,
1968+ harness . createOutputPath ( 'doc-images-set-z-order-output' ) ,
1969+ ] ,
1970+ } ;
1971+ } ,
16451972 'doc.toc.list' : async ( harness : ConformanceHarness ) : Promise < ScenarioInvocation > => {
16461973 const stateDir = await harness . createStateDir ( 'doc-toc-list-success' ) ;
16471974 const docPath = await harness . copyTocFixtureDoc ( 'doc-toc-list' , stateDir ) ;
0 commit comments