@@ -1331,6 +1331,33 @@ describe('Image directive', () => {
13311331 ) ;
13321332 } ) ;
13331333
1334+ it ( 'should pass calculated height to placeholder loader based on aspect ratio' , ( ) => {
1335+ const placeholderLoaderWithHeight = ( config : ImageLoaderConfig ) => {
1336+ const widthStr = config . width ? `w=${ config . width } ` : '' ;
1337+ const heightStr = config . height ? `h=${ config . height } ` : '' ;
1338+ const phStr = config . isPlaceholder ? 'ph=true' : '' ;
1339+ const params = [ widthStr , heightStr , phStr ] . filter ( ( p ) => p ) . join ( '&' ) ;
1340+ return `${ IMG_BASE_URL } /${ config . src } ${ params ? '?' + params : '' } ` ;
1341+ } ;
1342+ const imageConfig = {
1343+ placeholderResolution : 30 ,
1344+ } ;
1345+ setupTestingModule ( { imageLoader : placeholderLoaderWithHeight , imageConfig} ) ;
1346+ const template = '<img ngSrc="path/img.png" width="400" height="200" placeholder />' ;
1347+
1348+ const fixture = createTestComponent ( template ) ;
1349+ fixture . detectChanges ( ) ;
1350+ const nativeElement = fixture . nativeElement as HTMLElement ;
1351+ const img = nativeElement . querySelector ( 'img' ) ! ;
1352+ const styles = parseInlineStyles ( img ) ;
1353+ // Aspect ratio is 400/200 = 2, placeholderResolution is 30
1354+ // Expected height: 30 / 2 = 15
1355+ // Double quotes removed to account for different browser behavior.
1356+ expect ( styles . get ( 'background-image' ) ?. replace ( / " / g, '' ) ) . toBe (
1357+ `url(${ IMG_BASE_URL } /path/img.png?w=30&h=15&ph=true)` ,
1358+ ) ;
1359+ } ) ;
1360+
13341361 it ( 'should apply a background blur to images with a placeholder' , ( ) => {
13351362 setupTestingModule ( { imageLoader} ) ;
13361363 const template =
@@ -1981,6 +2008,122 @@ describe('Image directive', () => {
19812008 ) ;
19822009 } ) ;
19832010
2011+ it ( 'should pass height to custom image loader based on aspect ratio' , ( ) => {
2012+ const imageLoader = ( config : ImageLoaderConfig ) => {
2013+ const widthStr = config . width ? `w=${ config . width } ` : '' ;
2014+ const heightStr = config . height ? `h=${ config . height } ` : '' ;
2015+ const params = [ widthStr , heightStr ] . filter ( ( p ) => p ) . join ( '&' ) ;
2016+ return `${ IMG_BASE_URL } /${ config . src } ${ params ? '?' + params : '' } ` ;
2017+ } ;
2018+ setupTestingModule ( { imageLoader} ) ;
2019+
2020+ const template = '<img ngSrc="img.png" width="150" height="50">' ;
2021+ const fixture = createTestComponent ( template ) ;
2022+ fixture . detectChanges ( ) ;
2023+
2024+ const nativeElement = fixture . nativeElement as HTMLElement ;
2025+ const img = nativeElement . querySelector ( 'img' ) ! ;
2026+ // For src without width, height should not be passed
2027+ expect ( img . src ) . toBe ( `${ IMG_BASE_URL } /img.png` ) ;
2028+ } ) ;
2029+
2030+ it ( 'should pass calculated height to custom image loader when generating srcsets' , ( ) => {
2031+ const imageLoader = ( config : ImageLoaderConfig ) => {
2032+ const widthStr = config . width ? `w=${ config . width } ` : '' ;
2033+ const heightStr = config . height ? `h=${ config . height } ` : '' ;
2034+ const params = [ widthStr , heightStr ] . filter ( ( p ) => p ) . join ( '&' ) ;
2035+ return `${ IMG_BASE_URL } /${ config . src } ${ params ? '?' + params : '' } ` ;
2036+ } ;
2037+ setupTestingModule ( { imageLoader} ) ;
2038+
2039+ const template = '<img ngSrc="img.png" width="150" height="50">' ;
2040+ const fixture = createTestComponent ( template ) ;
2041+ fixture . detectChanges ( ) ;
2042+
2043+ const nativeElement = fixture . nativeElement as HTMLElement ;
2044+ const img = nativeElement . querySelector ( 'img' ) ! ;
2045+ // Aspect ratio is 150/50 = 3, so for widths 150 and 300:
2046+ // height should be 50 and 100 respectively
2047+ expect ( img . srcset ) . toBe (
2048+ `${ IMG_BASE_URL } /img.png?w=150&h=50 1x, ${ IMG_BASE_URL } /img.png?w=300&h=100 2x` ,
2049+ ) ;
2050+ } ) ;
2051+
2052+ it ( 'should pass calculated height to custom image loader when generating responsive srcsets' , ( ) => {
2053+ const imageLoader = ( config : ImageLoaderConfig ) => {
2054+ const widthStr = config . width ? `w=${ config . width } ` : '' ;
2055+ const heightStr = config . height ? `h=${ config . height } ` : '' ;
2056+ const params = [ widthStr , heightStr ] . filter ( ( p ) => p ) . join ( '&' ) ;
2057+ return `${ IMG_BASE_URL } /${ config . src } ${ params ? '?' + params : '' } ` ;
2058+ } ;
2059+ setupTestingModule ( { imageLoader} ) ;
2060+
2061+ const template = '<img ngSrc="img.png" width="150" height="50" sizes="100vw">' ;
2062+ const fixture = createTestComponent ( template ) ;
2063+ fixture . detectChanges ( ) ;
2064+
2065+ const nativeElement = fixture . nativeElement as HTMLElement ;
2066+ const img = nativeElement . querySelector ( 'img' ) ! ;
2067+ // Aspect ratio is 150/50 = 3
2068+ // Expected heights: 640/3=213, 750/3=250, etc.
2069+ expect ( img . srcset ) . toBe (
2070+ `${ IMG_BASE_URL } /img.png?w=640&h=213 640w, ${ IMG_BASE_URL } /img.png?w=750&h=250 750w, ${ IMG_BASE_URL } /img.png?w=828&h=276 828w, ${ IMG_BASE_URL } /img.png?w=1080&h=360 1080w, ${ IMG_BASE_URL } /img.png?w=1200&h=400 1200w, ${ IMG_BASE_URL } /img.png?w=1920&h=640 1920w, ${ IMG_BASE_URL } /img.png?w=2048&h=683 2048w, ${ IMG_BASE_URL } /img.png?w=3840&h=1280 3840w` ,
2071+ ) ;
2072+ } ) ;
2073+
2074+ it ( 'should not pass height to custom image loader when height is not provided' , ( ) => {
2075+ const imageLoader = ( config : ImageLoaderConfig ) => {
2076+ const widthStr = config . width ? `w=${ config . width } ` : '' ;
2077+ const heightStr = config . height ? `h=${ config . height } ` : '' ;
2078+ const params = [ widthStr , heightStr ] . filter ( ( p ) => p ) . join ( '&' ) ;
2079+ return `${ IMG_BASE_URL } /${ config . src } ${ params ? '?' + params : '' } ` ;
2080+ } ;
2081+ setupTestingModule ( { imageLoader} ) ;
2082+
2083+ const template = '<img ngSrc="img.png" fill>' ;
2084+ const fixture = createTestComponent ( template ) ;
2085+ fixture . detectChanges ( ) ;
2086+
2087+ const nativeElement = fixture . nativeElement as HTMLElement ;
2088+ const img = nativeElement . querySelector ( 'img' ) ! ;
2089+ // No height provided (fill mode), so aspect ratio cannot be calculated
2090+ // In fill mode, a responsive srcset is generated but without height parameters
2091+ expect ( img . srcset ) . toBe (
2092+ `${ IMG_BASE_URL } /img.png?w=640 640w, ${ IMG_BASE_URL } /img.png?w=750 750w, ${ IMG_BASE_URL } /img.png?w=828 828w, ${ IMG_BASE_URL } /img.png?w=1080 1080w, ${ IMG_BASE_URL } /img.png?w=1200 1200w, ${ IMG_BASE_URL } /img.png?w=1920 1920w, ${ IMG_BASE_URL } /img.png?w=2048 2048w, ${ IMG_BASE_URL } /img.png?w=3840 3840w` ,
2093+ ) ;
2094+ } ) ;
2095+
2096+ it ( 'should pass height to custom image loaders' , ( ) => {
2097+ @Component ( {
2098+ selector : 'test-cmp' ,
2099+ standalone : false ,
2100+ template : `<img [ngSrc]="ngSrc" width="300" height="150" sizes="100vw" />` ,
2101+ } )
2102+ class TestComponent {
2103+ ngSrc = `img.png` ;
2104+ }
2105+ const imageLoader = ( config : ImageLoaderConfig ) => {
2106+ const params : string [ ] = [ ] ;
2107+ if ( config . width ) {
2108+ params . push ( `w=${ config . width } ` ) ;
2109+ }
2110+ if ( config . height ) {
2111+ params . push ( `h=${ config . height } ` ) ;
2112+ }
2113+ const query = params . length ? `?${ params . join ( '&' ) } ` : '' ;
2114+ return `${ IMG_BASE_URL } /${ config . src } ${ query } ` ;
2115+ } ;
2116+ setupTestingModule ( { imageLoader, component : TestComponent } ) ;
2117+ const fixture = TestBed . createComponent ( TestComponent ) ;
2118+ fixture . detectChanges ( ) ;
2119+
2120+ let nativeElement = fixture . nativeElement as HTMLElement ;
2121+ let imgs = nativeElement . querySelectorAll ( 'img' ) ! ;
2122+ expect ( imgs [ 0 ] . getAttribute ( 'srcset' ) ) . toBe (
2123+ `${ IMG_BASE_URL } /img.png?w=640&h=320 640w, ${ IMG_BASE_URL } /img.png?w=750&h=375 750w, ${ IMG_BASE_URL } /img.png?w=828&h=414 828w, ${ IMG_BASE_URL } /img.png?w=1080&h=540 1080w, ${ IMG_BASE_URL } /img.png?w=1200&h=600 1200w, ${ IMG_BASE_URL } /img.png?w=1920&h=960 1920w, ${ IMG_BASE_URL } /img.png?w=2048&h=1024 2048w, ${ IMG_BASE_URL } /img.png?w=3840&h=1920 3840w` ,
2124+ ) ;
2125+ } ) ;
2126+
19842127 it ( 'should set `src` to an image URL that does not include a default width parameter' , ( ) => {
19852128 const imageLoader = ( config : ImageLoaderConfig ) => {
19862129 const widthStr = config . width ? `?w=${ config . width } ` : `` ;
0 commit comments