mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(common): rename rawSrc -> ngSrc in NgOptimizedImage directive (#47362)
As an ongoing effort to stabilize the NgOptimizedImage API before existing the Developer Preview, this commit renames the `rawSrc` attribute used for the NgOptimizedImage selector matching to `ngSrc`. The `rawSrcset` is also renamed to `ngSrcset` for consistency. The motivation for this change is to align the attribute name better with other built-in directives, such as `ngFor`, `ngIf`, `ngClass`, `ngStyle`, etc. Note: this is technically a breaking change, but since the NgOptimizedImage directive is in the Developer Preview mode, we land the change in a patch branch. PR Close #47362
This commit is contained in:
committed by
Dylan Hunn
parent
710d1da4ed
commit
ca7bf65933
@@ -7,8 +7,8 @@
|
||||
*/
|
||||
|
||||
// Assembles directive details string, useful for error messages.
|
||||
export function imgDirectiveDetails(rawSrc: string, includeRawSrc = true) {
|
||||
const rawSrcInfo =
|
||||
includeRawSrc ? `(activated on an <img> element with the \`rawSrc="${rawSrc}"\`) ` : '';
|
||||
return `The NgOptimizedImage directive ${rawSrcInfo}has detected that`;
|
||||
export function imgDirectiveDetails(ngSrc: string, includeNgSrc = true) {
|
||||
const ngSrcInfo =
|
||||
includeNgSrc ? `(activated on an <img> element with the \`ngSrc="${ngSrc}"\`) ` : '';
|
||||
return `The NgOptimizedImage directive ${ngSrcInfo}has detected that`;
|
||||
}
|
||||
|
||||
@@ -118,10 +118,10 @@ function throwUnexpectedAbsoluteUrlError(path: string, url: string): never {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.INVALID_LOADER_ARGUMENTS,
|
||||
ngDevMode &&
|
||||
`Image loader has detected a \`<img>\` tag with an invalid \`rawSrc\` attribute: ${
|
||||
`Image loader has detected a \`<img>\` tag with an invalid \`ngSrc\` attribute: ${
|
||||
url}. ` +
|
||||
`This image loader expects \`rawSrc\` to be a relative URL - ` +
|
||||
`This image loader expects \`ngSrc\` to be a relative URL - ` +
|
||||
`however the provided value is an absolute URL. ` +
|
||||
`To fix this, provide \`rawSrc\` as a path relative to the base URL ` +
|
||||
`To fix this, provide \`ngSrc\` as a path relative to the base URL ` +
|
||||
`configured for this loader (\`${path}\`).`);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import {getUrl} from './url';
|
||||
*/
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class LCPImageObserver implements OnDestroy {
|
||||
// Map of full image URLs -> original `rawSrc` values.
|
||||
// Map of full image URLs -> original `ngSrc` values.
|
||||
private images = new Map<string, string>();
|
||||
// Keep track of images for which `console.warn` was produced.
|
||||
private alreadyWarned = new Set<string>();
|
||||
@@ -65,8 +65,8 @@ export class LCPImageObserver implements OnDestroy {
|
||||
// Exclude `data:` and `blob:` URLs, since they are not supported by the directive.
|
||||
if (imgSrc.startsWith('data:') || imgSrc.startsWith('blob:')) return;
|
||||
|
||||
const imgRawSrc = this.images.get(imgSrc);
|
||||
if (imgRawSrc && !this.alreadyWarned.has(imgSrc)) {
|
||||
const imgNgSrc = this.images.get(imgSrc);
|
||||
if (imgNgSrc && !this.alreadyWarned.has(imgSrc)) {
|
||||
this.alreadyWarned.add(imgSrc);
|
||||
logMissingPriorityWarning(imgSrc);
|
||||
}
|
||||
@@ -75,9 +75,9 @@ export class LCPImageObserver implements OnDestroy {
|
||||
return observer;
|
||||
}
|
||||
|
||||
registerImage(rewrittenSrc: string, rawSrc: string) {
|
||||
registerImage(rewrittenSrc: string, originalNgSrc: string) {
|
||||
if (!this.observer) return;
|
||||
this.images.set(getUrl(rewrittenSrc, this.window!).href, rawSrc);
|
||||
this.images.set(getUrl(rewrittenSrc, this.window!).href, originalNgSrc);
|
||||
}
|
||||
|
||||
unregisterImage(rewrittenSrc: string) {
|
||||
@@ -93,8 +93,8 @@ export class LCPImageObserver implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
function logMissingPriorityWarning(rawSrc: string) {
|
||||
const directiveDetails = imgDirectiveDetails(rawSrc);
|
||||
function logMissingPriorityWarning(ngSrc: string) {
|
||||
const directiveDetails = imgDirectiveDetails(ngSrc);
|
||||
console.warn(formatRuntimeError(
|
||||
RuntimeErrorCode.LCP_IMG_MISSING_PRIORITY,
|
||||
`${directiveDetails} this image is the Largest Contentful Paint (LCP) ` +
|
||||
|
||||
@@ -83,8 +83,8 @@ const OVERSIZED_IMAGE_TOLERANCE = 1000;
|
||||
* Follow the steps below to enable and use the directive:
|
||||
* 1. Import it into the necessary NgModule or a standalone Component.
|
||||
* 2. Optionally provide an `ImageLoader` if you use an image hosting service.
|
||||
* 3. Update the necessary `<img>` tags in templates and replace `src` attributes with `rawSrc`.
|
||||
* Using a `rawSrc` allows the directive to control when the `src` gets set, which triggers an image
|
||||
* 3. Update the necessary `<img>` tags in templates and replace `src` attributes with `ngSrc`.
|
||||
* Using a `ngSrc` allows the directive to control when the `src` gets set, which triggers an image
|
||||
* download.
|
||||
*
|
||||
* Step 1: import the `NgOptimizedImage` directive.
|
||||
@@ -110,7 +110,7 @@ const OVERSIZED_IMAGE_TOLERANCE = 1000;
|
||||
*
|
||||
* To use the **default loader**: no additional code changes are necessary. The URL returned by the
|
||||
* generic loader will always match the value of "src". In other words, this loader applies no
|
||||
* transformations to the resource URL and the value of the `rawSrc` attribute will be used as is.
|
||||
* transformations to the resource URL and the value of the `ngSrc` attribute will be used as is.
|
||||
*
|
||||
* To use an existing loader for a **third-party image service**: add the provider factory for your
|
||||
* chosen service to the `providers` array. In the example below, the Imgix loader is used:
|
||||
@@ -150,10 +150,10 @@ const OVERSIZED_IMAGE_TOLERANCE = 1000;
|
||||
* ],
|
||||
* ```
|
||||
*
|
||||
* Step 3: update `<img>` tags in templates to use `rawSrc` instead of `src`.
|
||||
* Step 3: update `<img>` tags in templates to use `ngSrc` instead of `src`.
|
||||
*
|
||||
* ```
|
||||
* <img rawSrc="logo.png" width="200" height="100">
|
||||
* <img ngSrc="logo.png" width="200" height="100">
|
||||
* ```
|
||||
*
|
||||
* @publicApi
|
||||
@@ -161,7 +161,7 @@ const OVERSIZED_IMAGE_TOLERANCE = 1000;
|
||||
*/
|
||||
@Directive({
|
||||
standalone: true,
|
||||
selector: 'img[rawSrc]',
|
||||
selector: 'img[ngSrc],img[rawSrc]',
|
||||
})
|
||||
export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
|
||||
private imageLoader = inject(IMAGE_LOADER);
|
||||
@@ -180,25 +180,48 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
|
||||
*/
|
||||
private _renderedSrc: string|null = null;
|
||||
|
||||
/**
|
||||
* Previously, the `rawSrc` attribute was used to activate the directive.
|
||||
* The attribute was renamed to `ngSrc` and this input just produces an error,
|
||||
* suggesting to switch to `ngSrc` instead.
|
||||
*
|
||||
* This error should be removed in v15.
|
||||
*
|
||||
* @nodoc
|
||||
* @deprecated Use `ngSrc` instead.
|
||||
*/
|
||||
@Input()
|
||||
set rawSrc(value: string) {
|
||||
if (ngDevMode) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.INVALID_INPUT,
|
||||
`${imgDirectiveDetails(value, false)} the \`rawSrc\` attribute was used ` +
|
||||
`to activate the directive. Newer version of the directive uses the \`ngSrc\` ` +
|
||||
`attribute instead. Please replace \`rawSrc\` with \`ngSrc\` and ` +
|
||||
`\`rawSrcset\` with \`ngSrcset\` attributes in the template to ` +
|
||||
`enable image optimizations.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the source image.
|
||||
* Image name will be processed by the image loader and the final URL will be applied as the `src`
|
||||
* property of the image.
|
||||
*/
|
||||
@Input() rawSrc!: string;
|
||||
@Input() ngSrc!: string;
|
||||
|
||||
/**
|
||||
* A comma separated list of width or density descriptors.
|
||||
* The image name will be taken from `rawSrc` and combined with the list of width or density
|
||||
* The image name will be taken from `ngSrc` and combined with the list of width or density
|
||||
* descriptors to generate the final `srcset` property of the image.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* <img rawSrc="hello.jpg" rawSrcset="100w, 200w" /> =>
|
||||
* <img ngSrc="hello.jpg" ngSrcset="100w, 200w" /> =>
|
||||
* <img src="path/hello.jpg" srcset="path/hello.jpg?w=100 100w, path/hello.jpg?w=200 200w" />
|
||||
* ```
|
||||
*/
|
||||
@Input() rawSrcset!: string;
|
||||
@Input() ngSrcset!: string;
|
||||
|
||||
/**
|
||||
* The intrinsic width of the image in pixels.
|
||||
@@ -249,7 +272,7 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
|
||||
/**
|
||||
* Value of the `src` attribute if set on the host `<img>` element.
|
||||
* This input is exclusively read to assert that `src` is not set in conflict
|
||||
* with `rawSrc` and that images don't start to load until a lazy loading strategy is set.
|
||||
* with `ngSrc` and that images don't start to load until a lazy loading strategy is set.
|
||||
* @internal
|
||||
*/
|
||||
@Input() src?: string;
|
||||
@@ -257,15 +280,15 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
|
||||
/**
|
||||
* Value of the `srcset` attribute if set on the host `<img>` element.
|
||||
* This input is exclusively read to assert that `srcset` is not set in conflict
|
||||
* with `rawSrcset` and that images don't start to load until a lazy loading strategy is set.
|
||||
* with `ngSrcset` and that images don't start to load until a lazy loading strategy is set.
|
||||
* @internal
|
||||
*/
|
||||
@Input() srcset?: string;
|
||||
|
||||
ngOnInit() {
|
||||
if (ngDevMode) {
|
||||
assertNonEmptyInput(this, 'rawSrc', this.rawSrc);
|
||||
assertValidRawSrcset(this, this.rawSrcset);
|
||||
assertNonEmptyInput(this, 'ngSrc', this.ngSrc);
|
||||
assertValidNgSrcset(this, this.ngSrcset);
|
||||
assertNoConflictingSrc(this);
|
||||
assertNoConflictingSrcset(this);
|
||||
assertNotBase64Image(this);
|
||||
@@ -275,7 +298,7 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
|
||||
assertNoImageDistortion(this, this.imgElement, this.renderer);
|
||||
if (this.priority) {
|
||||
const checker = this.injector.get(PreconnectLinkChecker);
|
||||
checker.assertPreconnect(this.getRewrittenSrc(), this.rawSrc);
|
||||
checker.assertPreconnect(this.getRewrittenSrc(), this.ngSrc);
|
||||
} else {
|
||||
// Monitor whether an image is an LCP element only in case
|
||||
// the `priority` attribute is missing. Otherwise, an image
|
||||
@@ -283,7 +306,7 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
|
||||
if (this.lcpObserver !== null) {
|
||||
const ngZone = this.injector.get(NgZone);
|
||||
ngZone.runOutsideAngular(() => {
|
||||
this.lcpObserver!.registerImage(this.getRewrittenSrc(), this.rawSrc);
|
||||
this.lcpObserver!.registerImage(this.getRewrittenSrc(), this.ngSrc);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -302,7 +325,7 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
|
||||
// The `src` and `srcset` attributes should be set last since other attributes
|
||||
// could affect the image's loading behavior.
|
||||
this.setHostAttribute('src', this.getRewrittenSrc());
|
||||
if (this.rawSrcset) {
|
||||
if (this.ngSrcset) {
|
||||
this.setHostAttribute('srcset', this.getRewrittenSrcset());
|
||||
}
|
||||
}
|
||||
@@ -310,7 +333,7 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (ngDevMode) {
|
||||
assertNoPostInitInputChange(
|
||||
this, changes, ['rawSrc', 'rawSrcset', 'width', 'height', 'priority']);
|
||||
this, changes, ['ngSrc', 'ngSrcset', 'width', 'height', 'priority']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +353,7 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
|
||||
// because if the developer uses rendered width instead of intrinsic width in the HTML width
|
||||
// attribute, the image requested may be too small for 2x+ screens.
|
||||
if (!this._renderedSrc) {
|
||||
const imgConfig = {src: this.rawSrc};
|
||||
const imgConfig = {src: this.ngSrc};
|
||||
// Cache calculated image src to reuse it later in the code.
|
||||
this._renderedSrc = this.imageLoader(imgConfig);
|
||||
}
|
||||
@@ -338,11 +361,11 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
private getRewrittenSrcset(): string {
|
||||
const widthSrcSet = VALID_WIDTH_DESCRIPTOR_SRCSET.test(this.rawSrcset);
|
||||
const finalSrcs = this.rawSrcset.split(',').filter(src => src !== '').map(srcStr => {
|
||||
const widthSrcSet = VALID_WIDTH_DESCRIPTOR_SRCSET.test(this.ngSrcset);
|
||||
const finalSrcs = this.ngSrcset.split(',').filter(src => src !== '').map(srcStr => {
|
||||
srcStr = srcStr.trim();
|
||||
const width = widthSrcSet ? parseFloat(srcStr) : parseFloat(srcStr) * this.width!;
|
||||
return `${this.imageLoader({src: this.rawSrc, width})} ${srcStr}`;
|
||||
return `${this.imageLoader({src: this.ngSrc, width})} ${srcStr}`;
|
||||
});
|
||||
return finalSrcs.join(', ');
|
||||
}
|
||||
@@ -386,9 +409,9 @@ function assertNoConflictingSrc(dir: NgOptimizedImage) {
|
||||
if (dir.src) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.UNEXPECTED_SRC_ATTR,
|
||||
`${imgDirectiveDetails(dir.rawSrc)} both \`src\` and \`rawSrc\` have been set. ` +
|
||||
`${imgDirectiveDetails(dir.ngSrc)} both \`src\` and \`ngSrc\` have been set. ` +
|
||||
`Supplying both of these attributes breaks lazy loading. ` +
|
||||
`The NgOptimizedImage directive sets \`src\` itself based on the value of \`rawSrc\`. ` +
|
||||
`The NgOptimizedImage directive sets \`src\` itself based on the value of \`ngSrc\`. ` +
|
||||
`To fix this, please remove the \`src\` attribute.`);
|
||||
}
|
||||
}
|
||||
@@ -400,43 +423,43 @@ function assertNoConflictingSrcset(dir: NgOptimizedImage) {
|
||||
if (dir.srcset) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.UNEXPECTED_SRCSET_ATTR,
|
||||
`${imgDirectiveDetails(dir.rawSrc)} both \`srcset\` and \`rawSrcset\` have been set. ` +
|
||||
`${imgDirectiveDetails(dir.ngSrc)} both \`srcset\` and \`ngSrcset\` have been set. ` +
|
||||
`Supplying both of these attributes breaks lazy loading. ` +
|
||||
`The NgOptimizedImage directive sets \`srcset\` itself based on the value of ` +
|
||||
`\`rawSrcset\`. To fix this, please remove the \`srcset\` attribute.`);
|
||||
`\`ngSrcset\`. To fix this, please remove the \`srcset\` attribute.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the `rawSrc` is not a Base64-encoded image.
|
||||
* Verifies that the `ngSrc` is not a Base64-encoded image.
|
||||
*/
|
||||
function assertNotBase64Image(dir: NgOptimizedImage) {
|
||||
let rawSrc = dir.rawSrc.trim();
|
||||
if (rawSrc.startsWith('data:')) {
|
||||
if (rawSrc.length > BASE64_IMG_MAX_LENGTH_IN_ERROR) {
|
||||
rawSrc = rawSrc.substring(0, BASE64_IMG_MAX_LENGTH_IN_ERROR) + '...';
|
||||
let ngSrc = dir.ngSrc.trim();
|
||||
if (ngSrc.startsWith('data:')) {
|
||||
if (ngSrc.length > BASE64_IMG_MAX_LENGTH_IN_ERROR) {
|
||||
ngSrc = ngSrc.substring(0, BASE64_IMG_MAX_LENGTH_IN_ERROR) + '...';
|
||||
}
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.INVALID_INPUT,
|
||||
`${imgDirectiveDetails(dir.rawSrc, false)} \`rawSrc\` is a Base64-encoded string ` +
|
||||
`(${rawSrc}). NgOptimizedImage does not support Base64-encoded strings. ` +
|
||||
`${imgDirectiveDetails(dir.ngSrc, false)} \`ngSrc\` is a Base64-encoded string ` +
|
||||
`(${ngSrc}). NgOptimizedImage does not support Base64-encoded strings. ` +
|
||||
`To fix this, disable the NgOptimizedImage directive for this element ` +
|
||||
`by removing \`rawSrc\` and using a standard \`src\` attribute instead.`);
|
||||
`by removing \`ngSrc\` and using a standard \`src\` attribute instead.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the `rawSrc` is not a Blob URL.
|
||||
* Verifies that the `ngSrc` is not a Blob URL.
|
||||
*/
|
||||
function assertNotBlobUrl(dir: NgOptimizedImage) {
|
||||
const rawSrc = dir.rawSrc.trim();
|
||||
if (rawSrc.startsWith('blob:')) {
|
||||
const ngSrc = dir.ngSrc.trim();
|
||||
if (ngSrc.startsWith('blob:')) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.INVALID_INPUT,
|
||||
`${imgDirectiveDetails(dir.rawSrc)} \`rawSrc\` was set to a blob URL (${rawSrc}). ` +
|
||||
`${imgDirectiveDetails(dir.ngSrc)} \`ngSrc\` was set to a blob URL (${ngSrc}). ` +
|
||||
`Blob URLs are not supported by the NgOptimizedImage directive. ` +
|
||||
`To fix this, disable the NgOptimizedImage directive for this element ` +
|
||||
`by removing \`rawSrc\` and using a regular \`src\` attribute instead.`);
|
||||
`by removing \`ngSrc\` and using a regular \`src\` attribute instead.`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,17 +472,17 @@ function assertNonEmptyInput(dir: NgOptimizedImage, name: string, value: unknown
|
||||
if (!isString || isEmptyString) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.INVALID_INPUT,
|
||||
`${imgDirectiveDetails(dir.rawSrc)} \`${name}\` has an invalid value ` +
|
||||
`${imgDirectiveDetails(dir.ngSrc)} \`${name}\` has an invalid value ` +
|
||||
`(\`${value}\`). To fix this, change the value to a non-empty string.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the `rawSrcset` is in a valid format, e.g. "100w, 200w" or "1x, 2x".
|
||||
* Verifies that the `ngSrcset` is in a valid format, e.g. "100w, 200w" or "1x, 2x".
|
||||
*/
|
||||
export function assertValidRawSrcset(dir: NgOptimizedImage, value: unknown) {
|
||||
export function assertValidNgSrcset(dir: NgOptimizedImage, value: unknown) {
|
||||
if (value == null) return;
|
||||
assertNonEmptyInput(dir, 'rawSrcset', value);
|
||||
assertNonEmptyInput(dir, 'ngSrcset', value);
|
||||
const stringVal = value as string;
|
||||
const isValidWidthDescriptor = VALID_WIDTH_DESCRIPTOR_SRCSET.test(stringVal);
|
||||
const isValidDensityDescriptor = VALID_DENSITY_DESCRIPTOR_SRCSET.test(stringVal);
|
||||
@@ -472,8 +495,8 @@ export function assertValidRawSrcset(dir: NgOptimizedImage, value: unknown) {
|
||||
if (!isValidSrcset) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.INVALID_INPUT,
|
||||
`${imgDirectiveDetails(dir.rawSrc)} \`rawSrcset\` has an invalid value (\`${value}\`). ` +
|
||||
`To fix this, supply \`rawSrcset\` using a comma-separated list of one or more width ` +
|
||||
`${imgDirectiveDetails(dir.ngSrc)} \`ngSrcset\` has an invalid value (\`${value}\`). ` +
|
||||
`To fix this, supply \`ngSrcset\` using a comma-separated list of one or more width ` +
|
||||
`descriptors (e.g. "100w, 200w") or density descriptors (e.g. "1x, 2x").`);
|
||||
}
|
||||
}
|
||||
@@ -486,7 +509,7 @@ function assertUnderDensityCap(dir: NgOptimizedImage, value: string) {
|
||||
RuntimeErrorCode.INVALID_INPUT,
|
||||
`${
|
||||
imgDirectiveDetails(
|
||||
dir.rawSrc)} the \`rawSrcset\` contains an unsupported image density:` +
|
||||
dir.ngSrc)} the \`ngSrcset\` contains an unsupported image density:` +
|
||||
`\`${value}\`. NgOptimizedImage generally recommends a max image density of ` +
|
||||
`${RECOMMENDED_SRCSET_DENSITY_CAP}x but supports image densities up to ` +
|
||||
`${ABSOLUTE_SRCSET_DENSITY_CAP}x. The human eye cannot distinguish between image densities ` +
|
||||
@@ -503,7 +526,7 @@ function assertUnderDensityCap(dir: NgOptimizedImage, value: string) {
|
||||
function postInitInputChangeError(dir: NgOptimizedImage, inputName: string): {} {
|
||||
return new RuntimeError(
|
||||
RuntimeErrorCode.UNEXPECTED_INPUT_CHANGE,
|
||||
`${imgDirectiveDetails(dir.rawSrc)} \`${inputName}\` was updated after initialization. ` +
|
||||
`${imgDirectiveDetails(dir.ngSrc)} \`${inputName}\` was updated after initialization. ` +
|
||||
`The NgOptimizedImage directive will not react to this input change. ` +
|
||||
`To fix this, switch \`${inputName}\` a static value or wrap the image element ` +
|
||||
`in an *ngIf that is gated on the necessary value.`);
|
||||
@@ -517,12 +540,12 @@ function assertNoPostInitInputChange(
|
||||
inputs.forEach(input => {
|
||||
const isUpdated = changes.hasOwnProperty(input);
|
||||
if (isUpdated && !changes[input].isFirstChange()) {
|
||||
if (input === 'rawSrc') {
|
||||
// When the `rawSrc` input changes, we detect that only in the
|
||||
// `ngOnChanges` hook, thus the `rawSrc` is already set. We use
|
||||
// `rawSrc` in the error message, so we use a previous value, but
|
||||
if (input === 'ngSrc') {
|
||||
// When the `ngSrc` input changes, we detect that only in the
|
||||
// `ngOnChanges` hook, thus the `ngSrc` is already set. We use
|
||||
// `ngSrc` in the error message, so we use a previous value, but
|
||||
// not the updated one in it.
|
||||
dir = {rawSrc: changes[input].previousValue} as NgOptimizedImage;
|
||||
dir = {ngSrc: changes[input].previousValue} as NgOptimizedImage;
|
||||
}
|
||||
throw postInitInputChangeError(dir, input);
|
||||
}
|
||||
@@ -539,7 +562,7 @@ function assertGreaterThanZero(dir: NgOptimizedImage, inputValue: unknown, input
|
||||
if (!validNumber && !validString) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.INVALID_INPUT,
|
||||
`${imgDirectiveDetails(dir.rawSrc)} \`${inputName}\` has an invalid value ` +
|
||||
`${imgDirectiveDetails(dir.ngSrc)} \`${inputName}\` has an invalid value ` +
|
||||
`(\`${inputValue}\`). To fix this, provide \`${inputName}\` ` +
|
||||
`as a number greater than 0.`);
|
||||
}
|
||||
@@ -583,7 +606,7 @@ function assertNoImageDistortion(
|
||||
if (inaccurateDimensions) {
|
||||
console.warn(formatRuntimeError(
|
||||
RuntimeErrorCode.INVALID_INPUT,
|
||||
`${imgDirectiveDetails(dir.rawSrc)} the aspect ratio of the image does not match ` +
|
||||
`${imgDirectiveDetails(dir.ngSrc)} the aspect ratio of the image does not match ` +
|
||||
`the aspect ratio indicated by the width and height attributes. ` +
|
||||
`\nIntrinsic image size: ${intrinsicWidth}w x ${intrinsicHeight}h ` +
|
||||
`(aspect-ratio: ${intrinsicAspectRatio}). \nSupplied width and height attributes: ` +
|
||||
@@ -592,7 +615,7 @@ function assertNoImageDistortion(
|
||||
} else if (stylingDistortion) {
|
||||
console.warn(formatRuntimeError(
|
||||
RuntimeErrorCode.INVALID_INPUT,
|
||||
`${imgDirectiveDetails(dir.rawSrc)} the aspect ratio of the rendered image ` +
|
||||
`${imgDirectiveDetails(dir.ngSrc)} the aspect ratio of the rendered image ` +
|
||||
`does not match the image's intrinsic aspect ratio. ` +
|
||||
`\nIntrinsic image size: ${intrinsicWidth}w x ${intrinsicHeight}h ` +
|
||||
`(aspect-ratio: ${intrinsicAspectRatio}). \nRendered image size: ` +
|
||||
@@ -602,8 +625,8 @@ function assertNoImageDistortion(
|
||||
`image styling. To fix this, adjust image styling. In most cases, ` +
|
||||
`adding "height: auto" or "width: auto" to the image styling will fix ` +
|
||||
`this issue.`));
|
||||
} else if (!dir.rawSrcset && nonZeroRenderedDimensions) {
|
||||
// If `rawSrcset` hasn't been set, sanity check the intrinsic size.
|
||||
} else if (!dir.ngSrcset && nonZeroRenderedDimensions) {
|
||||
// If `ngSrcset` hasn't been set, sanity check the intrinsic size.
|
||||
const recommendedWidth = RECOMMENDED_SRCSET_DENSITY_CAP * renderedWidth;
|
||||
const recommendedHeight = RECOMMENDED_SRCSET_DENSITY_CAP * renderedHeight;
|
||||
const oversizedWidth = (intrinsicWidth - recommendedWidth) >= OVERSIZED_IMAGE_TOLERANCE;
|
||||
@@ -611,7 +634,7 @@ function assertNoImageDistortion(
|
||||
if (oversizedWidth || oversizedHeight) {
|
||||
console.warn(formatRuntimeError(
|
||||
RuntimeErrorCode.OVERSIZED_IMAGE,
|
||||
`${imgDirectiveDetails(dir.rawSrc)} the intrinsic image is significantly ` +
|
||||
`${imgDirectiveDetails(dir.ngSrc)} the intrinsic image is significantly ` +
|
||||
`larger than necessary. ` +
|
||||
`\nRendered image size: ${renderedWidth}w x ${renderedHeight}h. ` +
|
||||
`\nIntrinsic image size: ${intrinsicWidth}w x ${intrinsicHeight}h. ` +
|
||||
@@ -619,7 +642,7 @@ function assertNoImageDistortion(
|
||||
recommendedHeight}h. ` +
|
||||
`\nNote: Recommended intrinsic image size is calculated assuming a maximum DPR of ` +
|
||||
`${RECOMMENDED_SRCSET_DENSITY_CAP}. To improve loading time, resize the image ` +
|
||||
`or consider using the "rawSrcset" and "sizes" attributes.`));
|
||||
`or consider using the "ngSrcset" and "sizes" attributes.`));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -635,7 +658,7 @@ function assertNonEmptyWidthAndHeight(dir: NgOptimizedImage) {
|
||||
if (missingAttributes.length > 0) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.REQUIRED_INPUT_MISSING,
|
||||
`${imgDirectiveDetails(dir.rawSrc)} these required attributes ` +
|
||||
`${imgDirectiveDetails(dir.ngSrc)} these required attributes ` +
|
||||
`are missing: ${missingAttributes.map(attr => `"${attr}"`).join(', ')}. ` +
|
||||
`Including "width" and "height" attributes will prevent image-related layout shifts. ` +
|
||||
`To fix this, include "width" and "height" attributes on the image tag.`);
|
||||
@@ -650,7 +673,7 @@ function assertValidLoadingInput(dir: NgOptimizedImage) {
|
||||
if (dir.loading && dir.priority) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.INVALID_INPUT,
|
||||
`${imgDirectiveDetails(dir.rawSrc)} the \`loading\` attribute ` +
|
||||
`${imgDirectiveDetails(dir.ngSrc)} the \`loading\` attribute ` +
|
||||
`was used on an image that was marked "priority". ` +
|
||||
`Setting \`loading\` on priority images is not allowed ` +
|
||||
`because these images will always be eagerly loaded. ` +
|
||||
@@ -660,7 +683,7 @@ function assertValidLoadingInput(dir: NgOptimizedImage) {
|
||||
if (typeof dir.loading === 'string' && !validInputs.includes(dir.loading)) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.INVALID_INPUT,
|
||||
`${imgDirectiveDetails(dir.rawSrc)} the \`loading\` attribute ` +
|
||||
`${imgDirectiveDetails(dir.ngSrc)} the \`loading\` attribute ` +
|
||||
`has an invalid value (\`${dir.loading}\`). ` +
|
||||
`To fix this, provide a valid value ("lazy", "eager", or "auto").`);
|
||||
}
|
||||
|
||||
@@ -96,9 +96,9 @@ export class PreconnectLinkChecker {
|
||||
* given src.
|
||||
*
|
||||
* @param rewrittenSrc src formatted with loader
|
||||
* @param rawSrc rawSrc value
|
||||
* @param originalNgSrc ngSrc value
|
||||
*/
|
||||
assertPreconnect(rewrittenSrc: string, rawSrc: string): void {
|
||||
assertPreconnect(rewrittenSrc: string, originalNgSrc: string): void {
|
||||
if (!this.window) return;
|
||||
|
||||
const imgUrl = getUrl(rewrittenSrc, this.window);
|
||||
@@ -118,8 +118,8 @@ export class PreconnectLinkChecker {
|
||||
if (!this.preconnectLinks.has(imgUrl.origin)) {
|
||||
console.warn(formatRuntimeError(
|
||||
RuntimeErrorCode.PRIORITY_IMG_MISSING_PRECONNECT_TAG,
|
||||
`${imgDirectiveDetails(rawSrc)} there is no preconnect tag present for this image. ` +
|
||||
`Preconnecting to the origin(s) that serve priority images ensures that these ` +
|
||||
`${imgDirectiveDetails(originalNgSrc)} there is no preconnect tag present for this ` +
|
||||
`image. Preconnecting to the origin(s) that serve priority images ensures that these ` +
|
||||
`images are delivered as soon as possible. To fix this, please add the following ` +
|
||||
`element into the <head> of the document:\n` +
|
||||
` <link rel="preconnect" href="${imgUrl.origin}">`));
|
||||
|
||||
@@ -14,7 +14,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {withHead} from '@angular/private/testing';
|
||||
|
||||
import {IMAGE_LOADER, ImageLoader, ImageLoaderConfig} from '../../src/directives/ng_optimized_image/image_loaders/image_loader';
|
||||
import {ABSOLUTE_SRCSET_DENSITY_CAP, assertValidRawSrcset, NgOptimizedImage, RECOMMENDED_SRCSET_DENSITY_CAP} from '../../src/directives/ng_optimized_image/ng_optimized_image';
|
||||
import {ABSOLUTE_SRCSET_DENSITY_CAP, assertValidNgSrcset, NgOptimizedImage, RECOMMENDED_SRCSET_DENSITY_CAP} from '../../src/directives/ng_optimized_image/ng_optimized_image';
|
||||
import {PRECONNECT_CHECK_BLOCKLIST} from '../../src/directives/ng_optimized_image/preconnect_link_checker';
|
||||
|
||||
describe('Image directive', () => {
|
||||
@@ -25,7 +25,7 @@ describe('Image directive', () => {
|
||||
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc="path/img.png" width="150" height="50" priority>';
|
||||
const template = '<img ngSrc="path/img.png" width="150" height="50" priority>';
|
||||
TestBed.overrideComponent(TestComponent, {set: {template: template}});
|
||||
|
||||
const _document = TestBed.inject(DOCUMENT);
|
||||
@@ -71,7 +71,7 @@ describe('Image directive', () => {
|
||||
it('should always reflect the width/height attributes if bound', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc="path/img.png" [width]="width" [height]="height">';
|
||||
const template = '<img ngSrc="path/img.png" [width]="width" [height]="height">';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -82,7 +82,43 @@ describe('Image directive', () => {
|
||||
});
|
||||
|
||||
describe('setup error handling', () => {
|
||||
it('should throw if both `src` and `rawSrc` are present', () => {
|
||||
it('should throw if both `src` and `ngSrc` are present', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img ngSrc="path/img.png" src="path/img2.png" width="100" height="50">';
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02950: The NgOptimizedImage directive (activated on an <img> ' +
|
||||
'element with the `ngSrc="path/img.png"`) has detected that both ' +
|
||||
'`src` and `ngSrc` have been set. Supplying both of these attributes ' +
|
||||
'breaks lazy loading. The NgOptimizedImage directive sets `src` ' +
|
||||
'itself based on the value of `ngSrc`. To fix this, please remove ' +
|
||||
'the `src` attribute.');
|
||||
});
|
||||
|
||||
it('should throw if both `ngSrc` and `srcset` is present', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template =
|
||||
'<img ngSrc="img-100.png" srcset="img-100.png 100w, img-200.png 200w" width="100" height="50">';
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02951: The NgOptimizedImage directive (activated on an <img> ' +
|
||||
'element with the `ngSrc="img-100.png"`) has detected that both ' +
|
||||
'`srcset` and `ngSrcset` have been set. Supplying both of these ' +
|
||||
'attributes breaks lazy loading. ' +
|
||||
'The NgOptimizedImage directive sets `srcset` itself based ' +
|
||||
'on the value of `ngSrcset`. To fix this, please remove the `srcset` ' +
|
||||
'attribute.');
|
||||
});
|
||||
|
||||
it('should throw if an old `rawSrc` is present', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc="path/img.png" src="path/img2.png" width="100" height="50">';
|
||||
@@ -91,50 +127,29 @@ describe('Image directive', () => {
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02950: The NgOptimizedImage directive (activated on an <img> ' +
|
||||
'element with the `rawSrc="path/img.png"`) has detected that both ' +
|
||||
'`src` and `rawSrc` have been set. Supplying both of these attributes ' +
|
||||
'breaks lazy loading. The NgOptimizedImage directive sets `src` ' +
|
||||
'itself based on the value of `rawSrc`. To fix this, please remove ' +
|
||||
'the `src` attribute.');
|
||||
'NG02952: The NgOptimizedImage directive has detected that the `rawSrc` ' +
|
||||
'attribute was used to activate the directive. Newer version of the directive uses ' +
|
||||
'the `ngSrc` attribute instead. Please replace `rawSrc` with `ngSrc` and ' +
|
||||
'`rawSrcset` with `ngSrcset` attributes in the template to enable image optimizations.');
|
||||
});
|
||||
|
||||
it('should throw if both `rawSrc` and `srcset` is present', () => {
|
||||
it('should throw if `ngSrc` contains a Base64-encoded image (that starts with `data:`)', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template =
|
||||
'<img rawSrc="img-100.png" srcset="img-100.png 100w, img-200.png 200w" width="100" height="50">';
|
||||
expect(() => {
|
||||
const template = '<img ngSrc="' + ANGULAR_LOGO_BASE64 + '" width="50" height="50">';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02951: The NgOptimizedImage directive (activated on an <img> ' +
|
||||
'element with the `rawSrc="img-100.png"`) has detected that both ' +
|
||||
'`srcset` and `rawSrcset` have been set. Supplying both of these ' +
|
||||
'attributes breaks lazy loading. ' +
|
||||
'The NgOptimizedImage directive sets `srcset` itself based ' +
|
||||
'on the value of `rawSrcset`. To fix this, please remove the `srcset` ' +
|
||||
'attribute.');
|
||||
});
|
||||
|
||||
it('should throw if `rawSrc` contains a Base64-encoded image (that starts with `data:`)', () => {
|
||||
setupTestingModule();
|
||||
|
||||
expect(() => {
|
||||
const template = '<img rawSrc="' + ANGULAR_LOGO_BASE64 + '" width="50" height="50">';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02952: The NgOptimizedImage directive has detected that `rawSrc` ' +
|
||||
'NG02952: The NgOptimizedImage directive has detected that `ngSrc` ' +
|
||||
'is a Base64-encoded string (data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDov...). ' +
|
||||
'NgOptimizedImage does not support Base64-encoded strings. ' +
|
||||
'To fix this, disable the NgOptimizedImage directive for this element ' +
|
||||
'by removing `rawSrc` and using a standard `src` attribute instead.');
|
||||
'by removing `ngSrc` and using a standard `src` attribute instead.');
|
||||
});
|
||||
|
||||
it('should throw if `rawSrc` contains a `blob:` URL', (done) => {
|
||||
it('should throw if `ngSrc` contains a `blob:` URL', (done) => {
|
||||
// Domino does not support canvas elements properly,
|
||||
// so run this test only in a browser.
|
||||
if (!isBrowser) {
|
||||
@@ -151,9 +166,9 @@ describe('Image directive', () => {
|
||||
// Note: use RegExp to partially match the error message, since the blob URL
|
||||
// is created dynamically, so it might be different for each invocation.
|
||||
const errorMessageRegExp =
|
||||
/NG02952: The NgOptimizedImage directive (.*?) has detected that `rawSrc` was set to a blob URL \(blob:/;
|
||||
/NG02952: The NgOptimizedImage directive (.*?) has detected that `ngSrc` was set to a blob URL \(blob:/;
|
||||
expect(() => {
|
||||
const template = '<img rawSrc="' + blobURL + '" width="50" height="50">';
|
||||
const template = '<img ngSrc="' + blobURL + '" width="50" height="50">';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
}).toThrowError(errorMessageRegExp);
|
||||
@@ -164,14 +179,14 @@ describe('Image directive', () => {
|
||||
it('should throw if `width` and `height` are not set', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc="img.png">';
|
||||
const template = '<img ngSrc="img.png">';
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02954: The NgOptimizedImage directive (activated on an <img> ' +
|
||||
'element with the `rawSrc="img.png"`) has detected that these ' +
|
||||
'element with the `ngSrc="img.png"`) has detected that these ' +
|
||||
'required attributes are missing: "width", "height". Including "width" and ' +
|
||||
'"height" attributes will prevent image-related layout shifts. ' +
|
||||
'To fix this, include "width" and "height" attributes on the image tag.');
|
||||
@@ -180,14 +195,14 @@ describe('Image directive', () => {
|
||||
it('should throw if `width` is not set', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc="img.png" height="10">';
|
||||
const template = '<img ngSrc="img.png" height="10">';
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02954: The NgOptimizedImage directive (activated on an <img> ' +
|
||||
'element with the `rawSrc="img.png"`) has detected that these ' +
|
||||
'element with the `ngSrc="img.png"`) has detected that these ' +
|
||||
'required attributes are missing: "width". Including "width" and ' +
|
||||
'"height" attributes will prevent image-related layout shifts. ' +
|
||||
'To fix this, include "width" and "height" attributes on the image tag.');
|
||||
@@ -196,14 +211,14 @@ describe('Image directive', () => {
|
||||
it('should throw if `width` is 0', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc="img.png" width="0" height="10">';
|
||||
const template = '<img ngSrc="img.png" width="0" height="10">';
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02952: The NgOptimizedImage directive (activated on an <img> ' +
|
||||
'element with the `rawSrc="img.png"`) has detected that `width` ' +
|
||||
'element with the `ngSrc="img.png"`) has detected that `width` ' +
|
||||
'has an invalid value (`0`). To fix this, provide `width` as ' +
|
||||
'a number greater than 0.');
|
||||
});
|
||||
@@ -211,14 +226,14 @@ describe('Image directive', () => {
|
||||
it('should throw if `width` has an invalid value', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc="img.png" width="10px" height="10">';
|
||||
const template = '<img ngSrc="img.png" width="10px" height="10">';
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02952: The NgOptimizedImage directive (activated on an <img> ' +
|
||||
'element with the `rawSrc="img.png"`) has detected that `width` ' +
|
||||
'element with the `ngSrc="img.png"`) has detected that `width` ' +
|
||||
'has an invalid value (`10px`). To fix this, provide `width` ' +
|
||||
'as a number greater than 0.');
|
||||
});
|
||||
@@ -226,14 +241,14 @@ describe('Image directive', () => {
|
||||
it('should throw if `height` is not set', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc="img.png" width="10">';
|
||||
const template = '<img ngSrc="img.png" width="10">';
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02954: The NgOptimizedImage directive (activated on an <img> ' +
|
||||
'element with the `rawSrc="img.png"`) has detected that these required ' +
|
||||
'element with the `ngSrc="img.png"`) has detected that these required ' +
|
||||
'attributes are missing: "height". Including "width" and "height" ' +
|
||||
'attributes will prevent image-related layout shifts. ' +
|
||||
'To fix this, include "width" and "height" attributes on the image tag.');
|
||||
@@ -242,14 +257,14 @@ describe('Image directive', () => {
|
||||
it('should throw if `height` is 0', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc="img.png" width="10" height="0">';
|
||||
const template = '<img ngSrc="img.png" width="10" height="0">';
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02952: The NgOptimizedImage directive (activated on an <img> ' +
|
||||
'element with the `rawSrc="img.png"`) has detected that `height` ' +
|
||||
'element with the `ngSrc="img.png"`) has detected that `height` ' +
|
||||
'has an invalid value (`0`). To fix this, provide `height` as a number ' +
|
||||
'greater than 0.');
|
||||
});
|
||||
@@ -257,50 +272,50 @@ describe('Image directive', () => {
|
||||
it('should throw if `height` has an invalid value', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc="img.png" width="10" height="10%">';
|
||||
const template = '<img ngSrc="img.png" width="10" height="10%">';
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02952: The NgOptimizedImage directive (activated on an <img> element ' +
|
||||
'with the `rawSrc="img.png"`) has detected that `height` has an invalid ' +
|
||||
'with the `ngSrc="img.png"`) has detected that `height` has an invalid ' +
|
||||
'value (`10%`). To fix this, provide `height` as a number greater than 0.');
|
||||
});
|
||||
|
||||
it('should throw if `rawSrc` value is not provided', () => {
|
||||
it('should throw if `ngSrc` value is not provided', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc>';
|
||||
const template = '<img ngSrc>';
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02952: The NgOptimizedImage directive (activated on an <img> ' +
|
||||
'element with the `rawSrc=""`) has detected that `rawSrc` has an ' +
|
||||
'element with the `ngSrc=""`) has detected that `ngSrc` has an ' +
|
||||
'invalid value (``). ' +
|
||||
'To fix this, change the value to a non-empty string.');
|
||||
});
|
||||
|
||||
it('should throw if `rawSrc` value is set to an empty string', () => {
|
||||
it('should throw if `ngSrc` value is set to an empty string', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc=" ">';
|
||||
const template = '<img ngSrc=" ">';
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02952: The NgOptimizedImage directive (activated on an <img> element ' +
|
||||
'with the `rawSrc=" "`) has detected that `rawSrc` has an invalid value ' +
|
||||
'with the `ngSrc=" "`) has detected that `ngSrc` has an invalid value ' +
|
||||
'(` `). To fix this, change the value to a non-empty string.');
|
||||
});
|
||||
|
||||
describe('invalid `rawSrcset` values', () => {
|
||||
const mockDirectiveInstance = {rawSrc: 'img.png'} as NgOptimizedImage;
|
||||
describe('invalid `ngSrcset` values', () => {
|
||||
const mockDirectiveInstance = {ngSrc: 'img.png'} as NgOptimizedImage;
|
||||
|
||||
it('should throw for empty rawSrcSet', () => {
|
||||
it('should throw for empty ngSrcSet', () => {
|
||||
const imageLoader = (config: ImageLoaderConfig) => {
|
||||
const width = config.width ? `-${config.width}` : ``;
|
||||
return window.location.origin + `/path/${config.src}${width}.png`;
|
||||
@@ -308,7 +323,7 @@ describe('Image directive', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const template = `
|
||||
<img rawSrc="img" rawSrcset="" width="100" height="50">
|
||||
<img ngSrc="img" ngSrcset="" width="100" height="50">
|
||||
`;
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
@@ -316,12 +331,12 @@ describe('Image directive', () => {
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02952: The NgOptimizedImage directive (activated on an <img> ' +
|
||||
'element with the `rawSrc="img"`) has detected that `rawSrcset` ' +
|
||||
'element with the `ngSrc="img"`) has detected that `ngSrcset` ' +
|
||||
'has an invalid value (``). ' +
|
||||
'To fix this, change the value to a non-empty string.');
|
||||
});
|
||||
|
||||
it('should throw for invalid rawSrcSet', () => {
|
||||
it('should throw for invalid ngSrcSet', () => {
|
||||
const imageLoader = (config: ImageLoaderConfig) => {
|
||||
const width = config.width ? `-${config.width}` : ``;
|
||||
return window.location.origin + `/path/${config.src}${width}.png`;
|
||||
@@ -329,7 +344,7 @@ describe('Image directive', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const template = `
|
||||
<img rawSrc="img" rawSrcset="100q, 200q" width="100" height="50">
|
||||
<img ngSrc="img" ngSrcset="100q, 200q" width="100" height="50">
|
||||
`;
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
@@ -337,13 +352,13 @@ describe('Image directive', () => {
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02952: The NgOptimizedImage directive (activated on an <img> element ' +
|
||||
'with the `rawSrc="img"`) has detected that `rawSrcset` has an invalid value ' +
|
||||
'(`100q, 200q`). To fix this, supply `rawSrcset` using a comma-separated list ' +
|
||||
'with the `ngSrc="img"`) has detected that `ngSrcset` has an invalid value ' +
|
||||
'(`100q, 200q`). To fix this, supply `ngSrcset` using a comma-separated list ' +
|
||||
'of one or more width descriptors (e.g. "100w, 200w") or density descriptors ' +
|
||||
'(e.g. "1x, 2x").');
|
||||
});
|
||||
|
||||
it('should throw if rawSrcset exceeds the density cap', () => {
|
||||
it('should throw if ngSrcset exceeds the density cap', () => {
|
||||
const imageLoader = (config: ImageLoaderConfig) => {
|
||||
const width = config.width ? `-${config.width}` : ``;
|
||||
return window.location.origin + `/path/${config.src}${width}.png`;
|
||||
@@ -351,7 +366,7 @@ describe('Image directive', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const template = `
|
||||
<img rawSrc="img" rawSrcset="1x, 2x, 3x, 4x, 5x" width="100" height="50">
|
||||
<img ngSrc="img" ngSrcset="1x, 2x, 3x, 4x, 5x" width="100" height="50">
|
||||
`;
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
@@ -360,8 +375,8 @@ describe('Image directive', () => {
|
||||
.toThrowError(
|
||||
`NG0${
|
||||
RuntimeErrorCode
|
||||
.INVALID_INPUT}: The NgOptimizedImage directive (activated on an <img> element with the \`rawSrc="img"\`) ` +
|
||||
`has detected that the \`rawSrcset\` contains an unsupported image density:` +
|
||||
.INVALID_INPUT}: The NgOptimizedImage directive (activated on an <img> element with the \`ngSrc="img"\`) ` +
|
||||
`has detected that the \`ngSrcset\` contains an unsupported image density:` +
|
||||
`\`1x, 2x, 3x, 4x, 5x\`. NgOptimizedImage generally recommends a max image density of ` +
|
||||
`${RECOMMENDED_SRCSET_DENSITY_CAP}x but supports image densities up to ` +
|
||||
`${ABSOLUTE_SRCSET_DENSITY_CAP}x. The human eye cannot distinguish between image densities ` +
|
||||
@@ -372,7 +387,7 @@ describe('Image directive', () => {
|
||||
});
|
||||
|
||||
|
||||
it('should throw if rawSrcset exceeds the density cap with multiple digits', () => {
|
||||
it('should throw if ngSrcset exceeds the density cap with multiple digits', () => {
|
||||
const imageLoader = (config: ImageLoaderConfig) => {
|
||||
const width = config.width ? `-${config.width}` : ``;
|
||||
return window.location.origin + `/path/${config.src}${width}.png`;
|
||||
@@ -380,7 +395,7 @@ describe('Image directive', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const template = `
|
||||
<img rawSrc="img" rawSrcset="1x, 200x" width="100" height="50">
|
||||
<img ngSrc="img" ngSrcset="1x, 200x" width="100" height="50">
|
||||
`;
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
@@ -389,8 +404,8 @@ describe('Image directive', () => {
|
||||
.toThrowError(
|
||||
`NG0${
|
||||
RuntimeErrorCode
|
||||
.INVALID_INPUT}: The NgOptimizedImage directive (activated on an <img> element with the \`rawSrc="img"\`) ` +
|
||||
`has detected that the \`rawSrcset\` contains an unsupported image density:` +
|
||||
.INVALID_INPUT}: The NgOptimizedImage directive (activated on an <img> element with the \`ngSrc="img"\`) ` +
|
||||
`has detected that the \`ngSrcset\` contains an unsupported image density:` +
|
||||
`\`1x, 200x\`. NgOptimizedImage generally recommends a max image density of ` +
|
||||
`${RECOMMENDED_SRCSET_DENSITY_CAP}x but supports image densities up to ` +
|
||||
`${ABSOLUTE_SRCSET_DENSITY_CAP}x. The human eye cannot distinguish between image densities ` +
|
||||
@@ -402,75 +417,75 @@ describe('Image directive', () => {
|
||||
|
||||
it('should throw if width srcset is missing a comma', () => {
|
||||
expect(() => {
|
||||
assertValidRawSrcset(mockDirectiveInstance, '100w 200w');
|
||||
assertValidNgSrcset(mockDirectiveInstance, '100w 200w');
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
it('should throw if density srcset is missing a comma', () => {
|
||||
expect(() => {
|
||||
assertValidRawSrcset(mockDirectiveInstance, '1x 2x');
|
||||
assertValidNgSrcset(mockDirectiveInstance, '1x 2x');
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
it('should throw if density srcset has too many digits', () => {
|
||||
expect(() => {
|
||||
assertValidRawSrcset(mockDirectiveInstance, '100x, 2x');
|
||||
assertValidNgSrcset(mockDirectiveInstance, '100x, 2x');
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
it('should throw if width srcset includes a file name', () => {
|
||||
expect(() => {
|
||||
assertValidRawSrcset(mockDirectiveInstance, 'a.png 100w, b.png 200w');
|
||||
assertValidNgSrcset(mockDirectiveInstance, 'a.png 100w, b.png 200w');
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
it('should throw if density srcset includes a file name', () => {
|
||||
expect(() => {
|
||||
assertValidRawSrcset(mockDirectiveInstance, 'a.png 1x, b.png 2x');
|
||||
assertValidNgSrcset(mockDirectiveInstance, 'a.png 1x, b.png 2x');
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
it('should throw if srcset starts with a letter', () => {
|
||||
expect(() => {
|
||||
assertValidRawSrcset(mockDirectiveInstance, 'a100w, 200w');
|
||||
assertValidNgSrcset(mockDirectiveInstance, 'a100w, 200w');
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
it('should throw if srcset starts with another non-digit', () => {
|
||||
expect(() => {
|
||||
assertValidRawSrcset(mockDirectiveInstance, '--100w, 200w');
|
||||
assertValidNgSrcset(mockDirectiveInstance, '--100w, 200w');
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
it('should throw if first descriptor in srcset is junk', () => {
|
||||
expect(() => {
|
||||
assertValidRawSrcset(mockDirectiveInstance, 'foo, 1x');
|
||||
assertValidNgSrcset(mockDirectiveInstance, 'foo, 1x');
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
it('should throw if later descriptors in srcset are junk', () => {
|
||||
expect(() => {
|
||||
assertValidRawSrcset(mockDirectiveInstance, '100w, foo');
|
||||
assertValidNgSrcset(mockDirectiveInstance, '100w, foo');
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
it('should throw if srcset has a density descriptor after a width descriptor', () => {
|
||||
expect(() => {
|
||||
assertValidRawSrcset(mockDirectiveInstance, '100w, 1x');
|
||||
assertValidNgSrcset(mockDirectiveInstance, '100w, 1x');
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
it('should throw if srcset has a width descriptor after a density descriptor', () => {
|
||||
expect(() => {
|
||||
assertValidRawSrcset(mockDirectiveInstance, '1x, 200w');
|
||||
assertValidNgSrcset(mockDirectiveInstance, '1x, 200w');
|
||||
}).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
const inputs = [
|
||||
['rawSrc', 'new-img.png'], //
|
||||
['width', 10], //
|
||||
['height', 20], //
|
||||
['ngSrc', 'new-img.png'], //
|
||||
['width', 10], //
|
||||
['height', 20], //
|
||||
['priority', true]
|
||||
];
|
||||
inputs.forEach(([inputName, value]) => {
|
||||
@@ -478,7 +493,7 @@ describe('Image directive', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template =
|
||||
'<img [rawSrc]="rawSrc" [width]="width" [height]="height" [priority]="priority">';
|
||||
'<img [ngSrc]="ngSrc" [width]="width" [height]="height" [priority]="priority">';
|
||||
// Initial render
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
@@ -491,7 +506,7 @@ describe('Image directive', () => {
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02953: The NgOptimizedImage directive (activated on an <img> element ' +
|
||||
`with the \`rawSrc="img.png"\`) has detected that \`${inputName}\` was updated ` +
|
||||
`with the \`ngSrc="img.png"\`) has detected that \`${inputName}\` was updated ` +
|
||||
'after initialization. The NgOptimizedImage directive will not react ' +
|
||||
`to this input change. To fix this, switch \`${inputName}\` a static value or ` +
|
||||
'wrap the image element in an *ngIf that is gated on the necessary value.');
|
||||
@@ -503,7 +518,7 @@ describe('Image directive', () => {
|
||||
it('should eagerly load priority images', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc="path/img.png" width="150" height="50" priority>';
|
||||
const template = '<img ngSrc="path/img.png" width="150" height="50" priority>';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -515,7 +530,7 @@ describe('Image directive', () => {
|
||||
it('should lazily load non-priority images', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc="path/img.png" width="150" height="50">';
|
||||
const template = '<img ngSrc="path/img.png" width="150" height="50">';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -529,7 +544,7 @@ describe('Image directive', () => {
|
||||
it('should override the default loading behavior for non-priority images', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc="path/img.png" width="150" height="50" loading="eager">';
|
||||
const template = '<img ngSrc="path/img.png" width="150" height="50" loading="eager">';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -542,14 +557,14 @@ describe('Image directive', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template =
|
||||
'<img rawSrc="path/img.png" width="150" height="50" loading="eager" priority>';
|
||||
'<img ngSrc="path/img.png" width="150" height="50" loading="eager" priority>';
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02952: The NgOptimizedImage directive (activated on an <img> element ' +
|
||||
'with the `rawSrc="path/img.png"`) has detected that the `loading` attribute ' +
|
||||
'with the `ngSrc="path/img.png"`) has detected that the `loading` attribute ' +
|
||||
'was used on an image that was marked "priority". Setting `loading` on priority ' +
|
||||
'images is not allowed because these images will always be eagerly loaded. ' +
|
||||
'To fix this, remove the “loading” attribute from the priority image.');
|
||||
@@ -558,7 +573,7 @@ describe('Image directive', () => {
|
||||
it('should support setting loading priority to "auto"', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc="path/img.png" width="150" height="50" loading="auto">';
|
||||
const template = '<img ngSrc="path/img.png" width="150" height="50" loading="auto">';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -570,14 +585,14 @@ describe('Image directive', () => {
|
||||
it('should throw for invalid loading inputs', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc="path/img.png" width="150" height="150" loading="fast">';
|
||||
const template = '<img ngSrc="path/img.png" width="150" height="150" loading="fast">';
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
'NG02952: The NgOptimizedImage directive (activated on an <img> element ' +
|
||||
'with the `rawSrc="path/img.png"`) has detected that the `loading` attribute ' +
|
||||
'with the `ngSrc="path/img.png"`) has detected that the `loading` attribute ' +
|
||||
'has an invalid value (`fast`). To fix this, provide a valid value ("lazy", ' +
|
||||
'"eager", or "auto").');
|
||||
});
|
||||
@@ -587,7 +602,7 @@ describe('Image directive', () => {
|
||||
it('should be "high" for priority images', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc="path/img.png" width="150" height="50" priority>';
|
||||
const template = '<img ngSrc="path/img.png" width="150" height="50" priority>';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -599,7 +614,7 @@ describe('Image directive', () => {
|
||||
it('should be "auto" for non-priority images', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = '<img rawSrc="path/img.png" width="150" height="50">';
|
||||
const template = '<img ngSrc="path/img.png" width="150" height="50">';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -621,7 +636,7 @@ describe('Image directive', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const consoleWarnSpy = spyOn(console, 'warn');
|
||||
const template = '<img rawSrc="a.png" width="100" height="50" priority>';
|
||||
const template = '<img ngSrc="a.png" width="100" height="50" priority>';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -629,7 +644,7 @@ describe('Image directive', () => {
|
||||
expect(consoleWarnSpy.calls.argsFor(0)[0])
|
||||
.toBe(
|
||||
'NG02956: The NgOptimizedImage directive (activated on an <img> element ' +
|
||||
'with the `rawSrc="a.png"`) has detected that there is no preconnect tag ' +
|
||||
'with the `ngSrc="a.png"`) has detected that there is no preconnect tag ' +
|
||||
'present for this image. Preconnecting to the origin(s) that serve ' +
|
||||
'priority images ensures that these images are delivered as soon as ' +
|
||||
'possible. To fix this, please add the following element into the <head> ' +
|
||||
@@ -642,7 +657,7 @@ describe('Image directive', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const consoleWarnSpy = spyOn(console, 'warn');
|
||||
const template = '<img rawSrc="a.png" width="100" height="50">';
|
||||
const template = '<img ngSrc="a.png" width="100" height="50">';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -655,7 +670,7 @@ describe('Image directive', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const consoleWarnSpy = spyOn(console, 'warn');
|
||||
const template = '<img rawSrc="a.png" width="100" height="50" priority>';
|
||||
const template = '<img ngSrc="a.png" width="100" height="50" priority>';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -663,7 +678,7 @@ describe('Image directive', () => {
|
||||
expect(consoleWarnSpy.calls.argsFor(0)[0])
|
||||
.toBe(
|
||||
'NG02956: The NgOptimizedImage directive (activated on an <img> element ' +
|
||||
'with the `rawSrc="a.png"`) has detected that there is no preconnect tag ' +
|
||||
'with the `ngSrc="a.png"`) has detected that there is no preconnect tag ' +
|
||||
'present for this image. Preconnecting to the origin(s) that serve priority ' +
|
||||
'images ensures that these images are delivered as soon as possible. ' +
|
||||
'To fix this, please add the following element into the <head> of the document:' +
|
||||
@@ -677,7 +692,7 @@ describe('Image directive', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const consoleWarnSpy = spyOn(console, 'warn');
|
||||
const template = '<img rawSrc="a.png" width="100" height="50" priority>';
|
||||
const template = '<img ngSrc="a.png" width="100" height="50" priority>';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -685,7 +700,7 @@ describe('Image directive', () => {
|
||||
expect(consoleWarnSpy.calls.argsFor(0)[0])
|
||||
.toBe(
|
||||
'NG02956: The NgOptimizedImage directive (activated on an <img> element ' +
|
||||
'with the `rawSrc="a.png"`) has detected that there is no preconnect tag ' +
|
||||
'with the `ngSrc="a.png"`) has detected that there is no preconnect tag ' +
|
||||
'present for this image. Preconnecting to the origin(s) that serve priority ' +
|
||||
'images ensures that these images are delivered as soon as possible. ' +
|
||||
'To fix this, please add the following element into the <head> of the document:' +
|
||||
@@ -697,7 +712,7 @@ describe('Image directive', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const consoleWarnSpy = spyOn(console, 'warn');
|
||||
const template = '<img rawSrc="a.png" width="100" height="50" priority>';
|
||||
const template = '<img ngSrc="a.png" width="100" height="50" priority>';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -715,7 +730,7 @@ describe('Image directive', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const consoleWarnSpy = spyOn(console, 'warn');
|
||||
const template = '<img rawSrc="a.png" width="100" height="50" priority>';
|
||||
const template = '<img ngSrc="a.png" width="100" height="50" priority>';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -732,7 +747,7 @@ describe('Image directive', () => {
|
||||
setupTestingModule({imageLoader, extraProviders: providers});
|
||||
|
||||
const consoleWarnSpy = spyOn(console, 'warn');
|
||||
const template = '<img rawSrc="a.png" width="100" height="50" priority>';
|
||||
const template = '<img ngSrc="a.png" width="100" height="50" priority>';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -747,7 +762,7 @@ describe('Image directive', () => {
|
||||
setupTestingModule({imageLoader, extraProviders: providers});
|
||||
|
||||
const consoleWarnSpy = spyOn(console, 'warn');
|
||||
const template = '<img rawSrc="a.png" width="100" height="50" priority>';
|
||||
const template = '<img ngSrc="a.png" width="100" height="50" priority>';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -762,7 +777,7 @@ describe('Image directive', () => {
|
||||
setupTestingModule({imageLoader, extraProviders: providers});
|
||||
|
||||
const consoleWarnSpy = spyOn(console, 'warn');
|
||||
const template = '<img rawSrc="a.png" width="100" height="50" priority>';
|
||||
const template = '<img ngSrc="a.png" width="100" height="50" priority>';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -777,7 +792,7 @@ describe('Image directive', () => {
|
||||
setupTestingModule({imageLoader, extraProviders: providers});
|
||||
|
||||
const consoleWarnSpy = spyOn(console, 'warn');
|
||||
const template = '<img rawSrc="a.png" width="100" height="50" priority>';
|
||||
const template = '<img ngSrc="a.png" width="100" height="50" priority>';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -792,7 +807,7 @@ describe('Image directive', () => {
|
||||
];
|
||||
setupTestingModule({imageLoader, extraProviders: providers});
|
||||
|
||||
const template = '<img rawSrc="a.png" width="100" height="50" priority>';
|
||||
const template = '<img ngSrc="a.png" width="100" height="50" priority>';
|
||||
expect(() => {
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
@@ -806,10 +821,10 @@ describe('Image directive', () => {
|
||||
});
|
||||
|
||||
describe('loaders', () => {
|
||||
it('should set `src` to match `rawSrc` if image loader is not provided', () => {
|
||||
it('should set `src` to match `ngSrc` if image loader is not provided', () => {
|
||||
setupTestingModule();
|
||||
|
||||
const template = `<img rawSrc="${IMG_BASE_URL}/img.png" width="100" height="50">`;
|
||||
const template = `<img ngSrc="${IMG_BASE_URL}/img.png" width="100" height="50">`;
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -824,8 +839,8 @@ describe('Image directive', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const template = `
|
||||
<img rawSrc="img.png" width="150" height="50">
|
||||
<img rawSrc="img-2.png" width="150" height="50">
|
||||
<img ngSrc="img.png" width="150" height="50">
|
||||
<img ngSrc="img-2.png" width="150" height="50">
|
||||
`;
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
@@ -836,13 +851,13 @@ describe('Image directive', () => {
|
||||
expect(imgs[1].src.trim()).toBe(`${IMG_BASE_URL}/img-2.png`);
|
||||
});
|
||||
|
||||
it('should pass absolute URLs defined in the `rawSrc` to custom image loaders provided via the `IMAGE_LOADER` token',
|
||||
it('should pass absolute URLs defined in the `ngSrc` to custom image loaders provided via the `IMAGE_LOADER` token',
|
||||
() => {
|
||||
const imageLoader = (config: ImageLoaderConfig) => `${config.src}?rewritten=true`;
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const template = `
|
||||
<img rawSrc="${IMG_BASE_URL}/img.png" width="150" height="50">
|
||||
<img ngSrc="${IMG_BASE_URL}/img.png" width="150" height="50">
|
||||
`;
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
@@ -859,7 +874,7 @@ describe('Image directive', () => {
|
||||
};
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const template = '<img rawSrc="img.png" width="150" height="50">';
|
||||
const template = '<img ngSrc="img.png" width="150" height="50">';
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -868,7 +883,7 @@ describe('Image directive', () => {
|
||||
expect(img.src).toBe(`${IMG_BASE_URL}/img.png`);
|
||||
});
|
||||
|
||||
describe('`rawSrcset` values', () => {
|
||||
describe('`ngSrcset` values', () => {
|
||||
let imageLoader!: ImageLoader;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -878,11 +893,11 @@ describe('Image directive', () => {
|
||||
};
|
||||
});
|
||||
|
||||
it('should NOT set `srcset` if no `rawSrcset` value', () => {
|
||||
it('should NOT set `srcset` if no `ngSrcset` value', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const template = `
|
||||
<img rawSrc="img-100.png" width="100" height="50">
|
||||
<img ngSrc="img-100.png" width="100" height="50">
|
||||
`;
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
@@ -893,11 +908,11 @@ describe('Image directive', () => {
|
||||
expect(img.srcset).toBe('');
|
||||
});
|
||||
|
||||
it('should set the `srcset` using the `rawSrcset` value with width descriptors', () => {
|
||||
it('should set the `srcset` using the `ngSrcset` value with width descriptors', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const template = `
|
||||
<img rawSrc="img.png" rawSrcset="100w, 200w" width="100" height="50">
|
||||
<img ngSrc="img.png" ngSrcset="100w, 200w" width="100" height="50">
|
||||
`;
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
@@ -909,11 +924,11 @@ describe('Image directive', () => {
|
||||
.toBe(`${IMG_BASE_URL}/img.png?w=100 100w, ${IMG_BASE_URL}/img.png?w=200 200w`);
|
||||
});
|
||||
|
||||
it('should set the `srcset` using the `rawSrcset` value with density descriptors', () => {
|
||||
it('should set the `srcset` using the `ngSrcset` value with density descriptors', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const template = `
|
||||
<img rawSrc="img.png" rawSrcset="1x, 2x" width="100" height="50">
|
||||
<img ngSrc="img.png" ngSrcset="1x, 2x" width="100" height="50">
|
||||
`;
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
@@ -925,11 +940,11 @@ describe('Image directive', () => {
|
||||
.toBe(`${IMG_BASE_URL}/img.png?w=100 1x, ${IMG_BASE_URL}/img.png?w=200 2x`);
|
||||
});
|
||||
|
||||
it('should set the `srcset` if `rawSrcset` has only one src defined', () => {
|
||||
it('should set the `srcset` if `ngSrcset` has only one src defined', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const template = `
|
||||
<img rawSrc="img.png" rawSrcset="100w" width="100" height="50">
|
||||
<img ngSrc="img.png" ngSrcset="100w" width="100" height="50">
|
||||
`;
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
@@ -940,11 +955,11 @@ describe('Image directive', () => {
|
||||
expect(img.srcset.trim()).toBe(`${IMG_BASE_URL}/img.png?w=100 100w`);
|
||||
});
|
||||
|
||||
it('should set the `srcset` if `rawSrcSet` has extra spaces', () => {
|
||||
it('should set the `srcset` if `ngSrcSet` has extra spaces', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const template = `
|
||||
<img rawSrc="img.png" rawSrcset=" 100w, 200w " width="100" height="50">
|
||||
<img ngSrc="img.png" ngSrcset=" 100w, 200w " width="100" height="50">
|
||||
`;
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
@@ -956,11 +971,11 @@ describe('Image directive', () => {
|
||||
.toBe(`${IMG_BASE_URL}/img.png?w=100 100w, ${IMG_BASE_URL}/img.png?w=200 200w`);
|
||||
});
|
||||
|
||||
it('should set the `srcset` if `rawSrcSet` has a trailing comma', () => {
|
||||
it('should set the `srcset` if `ngSrcSet` has a trailing comma', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const template = `
|
||||
<img rawSrc="img.png" rawSrcset="1x, 2x," width="100" height="50">
|
||||
<img ngSrc="img.png" ngSrcset="1x, 2x," width="100" height="50">
|
||||
`;
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
@@ -972,11 +987,11 @@ describe('Image directive', () => {
|
||||
.toBe(`${IMG_BASE_URL}/img.png?w=100 1x, ${IMG_BASE_URL}/img.png?w=200 2x`);
|
||||
});
|
||||
|
||||
it('should set the `srcset` if `rawSrcSet` has 3+ srcs', () => {
|
||||
it('should set the `srcset` if `ngSrcSet` has 3+ srcs', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const template = `
|
||||
<img rawSrc="img.png" rawSrcset="100w, 200w, 300w" width="100" height="50">
|
||||
<img ngSrc="img.png" ngSrcset="100w, 200w, 300w" width="100" height="50">
|
||||
`;
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
@@ -991,11 +1006,11 @@ describe('Image directive', () => {
|
||||
`${IMG_BASE_URL}/img.png?w=300 300w`);
|
||||
});
|
||||
|
||||
it('should set the `srcset` if `rawSrcSet` has decimal density descriptors', () => {
|
||||
it('should set the `srcset` if `ngSrcSet` has decimal density descriptors', () => {
|
||||
setupTestingModule({imageLoader});
|
||||
|
||||
const template = `
|
||||
<img rawSrc="img.png" rawSrcset="1.75x, 2.5x, 3x" width="100" height="50">
|
||||
<img ngSrc="img.png" ngSrcset="1.75x, 2.5x, 3x" width="100" height="50">
|
||||
`;
|
||||
const fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
@@ -1032,7 +1047,7 @@ const ANGULAR_LOGO_BASE64 =
|
||||
class TestComponent {
|
||||
width = 100;
|
||||
height = 50;
|
||||
rawSrc = 'img.png';
|
||||
ngSrc = 'img.png';
|
||||
priority = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@ import {TestBed} from '@angular/core/testing';
|
||||
|
||||
const absoluteUrlError = (src: string, path: string) =>
|
||||
`NG02959: Image loader has detected a \`<img>\` tag with an invalid ` +
|
||||
`\`rawSrc\` attribute: ${src}. This image loader expects \`rawSrc\` ` +
|
||||
`\`ngSrc\` attribute: ${src}. This image loader expects \`ngSrc\` ` +
|
||||
`to be a relative URL - however the provided value is an absolute URL. ` +
|
||||
`To fix this, provide \`rawSrc\` as a path relative to the base URL ` +
|
||||
`To fix this, provide \`ngSrc\` as a path relative to the base URL ` +
|
||||
`configured for this loader (\`${path}\`).`;
|
||||
|
||||
const invalidPathError = (path: string, formats: string) =>
|
||||
|
||||
@@ -13,7 +13,7 @@ import {Component} from '@angular/core';
|
||||
selector: 'basic',
|
||||
standalone: true,
|
||||
imports: [NgOptimizedImage],
|
||||
template: `<img rawSrc="/e2e/a.png" width="150" height="150" priority>`,
|
||||
template: `<img ngSrc="/e2e/a.png" width="150" height="150" priority>`,
|
||||
providers: [{
|
||||
provide: IMAGE_LOADER,
|
||||
useValue: () => 'https://angular.io/assets/images/logos/angular/angular.svg'
|
||||
|
||||
+5
-5
@@ -36,7 +36,7 @@ describe('NgOptimizedImage directive', () => {
|
||||
expectErrorMessageInLogs(
|
||||
logs,
|
||||
'The NgOptimizedImage directive (activated on an \\u003Cimg> element ' +
|
||||
'with the \`rawSrc=\\"/e2e/b.png\\"`) has detected that ' +
|
||||
'with the \`ngSrc=\\"/e2e/b.png\\"`) has detected that ' +
|
||||
'the aspect ratio of the image does not match the aspect ratio indicated by the width and height attributes. ' +
|
||||
'\\nIntrinsic image size: 250w x 250h (aspect-ratio: 1). ' +
|
||||
'\\nSupplied width and height attributes: 26w x 30h (aspect-ratio: 0.8666666666666667). ' +
|
||||
@@ -45,7 +45,7 @@ describe('NgOptimizedImage directive', () => {
|
||||
expectErrorMessageInLogs(
|
||||
logs,
|
||||
'The NgOptimizedImage directive (activated on an \\u003Cimg> element ' +
|
||||
'with the \`rawSrc=\\"/e2e/b.png\\"`) has detected that ' +
|
||||
'with the \`ngSrc=\\"/e2e/b.png\\"`) has detected that ' +
|
||||
'the aspect ratio of the image does not match the aspect ratio indicated by the width and height attributes. ' +
|
||||
'\\nIntrinsic image size: 250w x 250h (aspect-ratio: 1). ' +
|
||||
'\\nSupplied width and height attributes: 24w x 240h (aspect-ratio: 0.1). ' +
|
||||
@@ -55,7 +55,7 @@ describe('NgOptimizedImage directive', () => {
|
||||
expectErrorMessageInLogs(
|
||||
logs,
|
||||
'The NgOptimizedImage directive (activated on an \\u003Cimg> element ' +
|
||||
'with the \`rawSrc=\\"/e2e/b.png\\"`) has detected that ' +
|
||||
'with the \`ngSrc=\\"/e2e/b.png\\"`) has detected that ' +
|
||||
'the aspect ratio of the rendered image does not match the image\'s intrinsic aspect ratio. ' +
|
||||
'\\nIntrinsic image size: 250w x 250h (aspect-ratio: 1). ' +
|
||||
'\\nRendered image size: 250w x 30h (aspect-ratio: 8.333333333333334). ' +
|
||||
@@ -66,7 +66,7 @@ describe('NgOptimizedImage directive', () => {
|
||||
expectErrorMessageInLogs(
|
||||
logs,
|
||||
'The NgOptimizedImage directive (activated on an \\u003Cimg> element ' +
|
||||
'with the \`rawSrc=\\"/e2e/b.png\\"`) has detected that ' +
|
||||
'with the \`ngSrc=\\"/e2e/b.png\\"`) has detected that ' +
|
||||
'the aspect ratio of the rendered image does not match the image\'s intrinsic aspect ratio. ' +
|
||||
'\\nIntrinsic image size: 250w x 250h (aspect-ratio: 1). ' +
|
||||
'\\nRendered image size: 30w x 250h (aspect-ratio: 0.12). ' +
|
||||
@@ -80,7 +80,7 @@ describe('NgOptimizedImage directive', () => {
|
||||
expectErrorMessageInLogs(
|
||||
logs,
|
||||
'The NgOptimizedImage directive (activated on an \\u003Cimg> element ' +
|
||||
'with the \`rawSrc=\\"/e2e/b.png\\"`) has detected that ' +
|
||||
'with the \`ngSrc=\\"/e2e/b.png\\"`) has detected that ' +
|
||||
'the aspect ratio of the image does not match the aspect ratio indicated by the width and height attributes. ' +
|
||||
'\\nIntrinsic image size: 250w x 250h (aspect-ratio: 1). ' +
|
||||
'\\nSupplied width and height attributes: 150w x 250h (aspect-ratio: 0.6). ' +
|
||||
|
||||
+20
-20
@@ -16,33 +16,33 @@ import {Component} from '@angular/core';
|
||||
template: `
|
||||
<!-- All the images in this template should not throw -->
|
||||
<!-- This image is here for the sake of making sure the "LCP image is priority" assertion is passed -->
|
||||
<img rawSrc="/e2e/logo-500w.jpg" width="500" height="500" priority>
|
||||
<img ngSrc="/e2e/logo-500w.jpg" width="500" height="500" priority>
|
||||
<br>
|
||||
<!-- width and height attributes exactly match the intrinsic size of image -->
|
||||
<img rawSrc="/e2e/a.png" width="25" height="25">
|
||||
<img ngSrc="/e2e/a.png" width="25" height="25">
|
||||
<br>
|
||||
<!-- supplied aspect ratio exactly matches intrinsic aspect ratio-->
|
||||
<img rawSrc="/e2e/a.png" width="250" height="250">
|
||||
<img rawSrc="/e2e/b.png" width="40" height="40">
|
||||
<img rawSrc="/e2e/b.png" width="240" height="240">
|
||||
<img ngSrc="/e2e/a.png" width="250" height="250">
|
||||
<img ngSrc="/e2e/b.png" width="40" height="40">
|
||||
<img ngSrc="/e2e/b.png" width="240" height="240">
|
||||
<br>
|
||||
<!-- supplied aspect ratio is similar to intrinsic aspect ratio -->
|
||||
<!-- Aspect-ratio: 0.93333333333 -->
|
||||
<img rawSrc="/e2e/b.png" width="28" height="30">
|
||||
<img ngSrc="/e2e/b.png" width="28" height="30">
|
||||
<!-- Aspect-ratio: 0.9 -->
|
||||
<img rawSrc="/e2e/b.png" width="27" height="30">
|
||||
<img ngSrc="/e2e/b.png" width="27" height="30">
|
||||
<!-- Aspect-ratio: 1.09375 -->
|
||||
<img rawSrc="/e2e/b.png" width="350" height="320">
|
||||
<img ngSrc="/e2e/b.png" width="350" height="320">
|
||||
<!-- Aspect-ratio: 1.0652173913 -->
|
||||
<img rawSrc="/e2e/b.png" width="245" height="230">
|
||||
<img ngSrc="/e2e/b.png" width="245" height="230">
|
||||
<br>
|
||||
<!-- Supplied aspect ratio is correct & image has 0x0 rendered size -->
|
||||
<img rawSrc="/e2e/a.png" width="25" height="25" style="display: none">
|
||||
<img ngSrc="/e2e/a.png" width="25" height="25" style="display: none">
|
||||
<br>
|
||||
<!-- styling is correct -->
|
||||
<img rawSrc="/e2e/a.png" width="25" height="25" style="width: 100%; height: 100%">
|
||||
<img rawSrc="/e2e/a.png" width="250" height="250" style="max-width: 100%; height: 100%">
|
||||
<img rawSrc="/e2e/a.png" width="25" height="25" style="height: 25%; width: 25%;">
|
||||
<img ngSrc="/e2e/a.png" width="25" height="25" style="width: 100%; height: 100%">
|
||||
<img ngSrc="/e2e/a.png" width="250" height="250" style="max-width: 100%; height: 100%">
|
||||
<img ngSrc="/e2e/a.png" width="25" height="25" style="height: 25%; width: 25%;">
|
||||
<br>
|
||||
`,
|
||||
})
|
||||
@@ -55,23 +55,23 @@ export class ImageDistortionPassingComponent {
|
||||
template: `
|
||||
<!-- With the exception of the priority image, all the images in this template should throw -->
|
||||
<!-- This image is here for the sake of making sure the "LCP image is priority" assertion is passed -->
|
||||
<img rawSrc="/e2e/logo-500w.jpg" width="500" height="500" priority>
|
||||
<img ngSrc="/e2e/logo-500w.jpg" width="500" height="500" priority>
|
||||
<br>
|
||||
<!-- These images should throw -->
|
||||
<!-- Supplied aspect ratio differs from intrinsic aspect ratio by > .1 -->
|
||||
<!-- Aspect-ratio: 0.86666666666 -->
|
||||
<img rawSrc="/e2e/b.png" width="26" height="30">
|
||||
<img ngSrc="/e2e/b.png" width="26" height="30">
|
||||
<!-- Aspect-ratio: 0.1 -->
|
||||
<img rawSrc="/e2e/b.png" width="24" height="240">
|
||||
<img ngSrc="/e2e/b.png" width="24" height="240">
|
||||
<!-- Supplied aspect ratio is incorrect & image has 0x0 rendered size -->
|
||||
<img rawSrc="/e2e/a.png" width="222" height="25" style="display: none">
|
||||
<img ngSrc="/e2e/a.png" width="222" height="25" style="display: none">
|
||||
<br>
|
||||
<!-- Image styling is causing distortion -->
|
||||
<div style="width: 300px; height: 300px">
|
||||
<img rawSrc="/e2e/b.png" width="250" height="250" style="width: 10%">
|
||||
<img rawSrc="/e2e/b.png" width="250" height="250" style="max-height: 10%">
|
||||
<img ngSrc="/e2e/b.png" width="250" height="250" style="width: 10%">
|
||||
<img ngSrc="/e2e/b.png" width="250" height="250" style="max-height: 10%">
|
||||
<!-- Images dimensions are incorrect AND image styling is incorrect -->
|
||||
<img rawSrc="/e2e/b.png" width="150" height="250" style="max-height: 10%">
|
||||
<img ngSrc="/e2e/b.png" width="150" height="250" style="max-height: 10%">
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
|
||||
@@ -18,12 +18,12 @@ import {Component} from '@angular/core';
|
||||
'b.png' should *not* be treated as an LCP element,
|
||||
since there is a bigger one right below it
|
||||
-->
|
||||
<img rawSrc="/e2e/b.png" width="5" height="5">
|
||||
<img ngSrc="/e2e/b.png" width="5" height="5">
|
||||
|
||||
<br>
|
||||
|
||||
<!-- 'a.png' should be treated as an LCP element -->
|
||||
<img rawSrc="/e2e/a.png" width="2500" height="2500">
|
||||
<img ngSrc="/e2e/a.png" width="2500" height="2500">
|
||||
|
||||
<br>
|
||||
|
||||
@@ -31,7 +31,7 @@ import {Component} from '@angular/core';
|
||||
'b.png' should *not* be treated as an LCP element here
|
||||
as well, since it's below the fold
|
||||
-->
|
||||
<img rawSrc="/e2e/b.png" width="10" height="10">
|
||||
<img ngSrc="/e2e/b.png" width="10" height="10">
|
||||
`,
|
||||
})
|
||||
export class LcpCheckComponent {
|
||||
|
||||
@@ -16,12 +16,12 @@ import {Component} from '@angular/core';
|
||||
template: `
|
||||
<!-- Image is rendered within threshold range-->
|
||||
<div style="width: 500px; height: 500px">
|
||||
<img rawSrc="/e2e/logo-500w.jpg" width="200" height="200" priority>
|
||||
<img ngSrc="/e2e/logo-500w.jpg" width="200" height="200" priority>
|
||||
</div>
|
||||
<!-- Image is rendered too small but rawSrcset set-->
|
||||
<!-- Image is rendered too small but ngSrcset set-->
|
||||
<div style="width: 300px; height: 300px">
|
||||
<img rawSrc="/e2e/logo-1500w.jpg" width="100" height="100" priority
|
||||
rawSrcset="100w, 200w">
|
||||
<img ngSrc="/e2e/logo-1500w.jpg" width="100" height="100" priority
|
||||
ngSrcset="100w, 200w">
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
@@ -36,7 +36,7 @@ export class OversizedImageComponentPassing {
|
||||
template: `
|
||||
<!-- Image is rendered too small -->
|
||||
<div style="width: 300px; height: 300px">
|
||||
<img rawSrc="/e2e/logo-1500w.jpg" width="100" height="100" priority>
|
||||
<img ngSrc="/e2e/logo-1500w.jpg" width="100" height="100" priority>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
|
||||
+3
-3
@@ -14,9 +14,9 @@ import {Component, Inject} from '@angular/core';
|
||||
standalone: true,
|
||||
imports: [NgOptimizedImage],
|
||||
template: `
|
||||
<img rawSrc="/e2e/a.png" width="50" height="50" priority>
|
||||
<img rawSrc="/e2e/b.png" width="50" height="50" priority>
|
||||
<img rawSrc="/e2e/c.png" width="50" height="50">
|
||||
<img ngSrc="/e2e/a.png" width="50" height="50" priority>
|
||||
<img ngSrc="/e2e/b.png" width="50" height="50" priority>
|
||||
<img ngSrc="/e2e/c.png" width="50" height="50">
|
||||
`,
|
||||
providers: [{
|
||||
provide: IMAGE_LOADER,
|
||||
|
||||
@@ -34,12 +34,12 @@ import {Component} from '@angular/core';
|
||||
`],
|
||||
template: `
|
||||
<h1>
|
||||
<img rawSrc="a.png" width="50" height="50" priority rawSrcset="1x, 2x">
|
||||
<img ngSrc="a.png" width="50" height="50" priority ngSrcset="1x, 2x">
|
||||
<span>Angular image app</span>
|
||||
</h1>
|
||||
<main>
|
||||
<div class="spacer"></div>
|
||||
<img rawSrc="hermes2.jpeg" rawSrcset="100w, 200w, 1000w, 2000w" width="1791" height="1008">
|
||||
<img ngSrc="hermes2.jpeg" ngSrcset="100w, 200w, 1000w, 2000w" width="1791" height="1008">
|
||||
</main>
|
||||
`,
|
||||
standalone: true,
|
||||
|
||||
Reference in New Issue
Block a user