;
- /** Position of transformation string in URL */
- transformationPosition?: 'path' | 'query';
- /** Enable responsive srcSet generation */
- responsive?: boolean;
- /** HTML sizes attribute for responsive images */
- sizes?: string;
- /** Custom device-width breakpoints */
- deviceBreakpoints?: number[];
- /** Custom image-specific breakpoints */
- imageBreakpoints?: number[];
- /** Format for the image */
- format?: Transformation['format'];
-}
+
diff --git a/imagekit-astro/tsup.config.ts b/imagekit-astro/tsup.config.ts
index fd85a36..38b990a 100644
--- a/imagekit-astro/tsup.config.ts
+++ b/imagekit-astro/tsup.config.ts
@@ -18,6 +18,7 @@ export default defineConfig({
'astro',
'astro:assets',
'@imagekit/javascript',
+ 'virtual:@imagekit/astro/config',
/\.astro$/,
],
})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9ff50dc..f04b8cc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -18,6 +18,9 @@ importers:
specifier: ^5.1.0
version: 5.3.0
devDependencies:
+ '@types/node':
+ specifier: ^20.19.39
+ version: 20.19.39
astro:
specifier: ^5.11.0
version: 5.18.1(@types/node@20.19.39)(rollup@4.60.1)(typescript@5.9.3)(yaml@2.8.3)
@@ -33,6 +36,9 @@ importers:
'@imagekit/astro':
specifier: workspace:*
version: link:../imagekit-astro
+ '@imagekit/javascript':
+ specifier: ^5.1.0
+ version: 5.3.0
astro:
specifier: ^5.18.1
version: 5.18.1(@types/node@20.19.39)(rollup@4.60.1)(typescript@5.9.3)(yaml@2.8.3)
diff --git a/scripts/test-all-versions.sh b/scripts/test-all-versions.sh
index 32ae934..703ee59 100755
--- a/scripts/test-all-versions.sh
+++ b/scripts/test-all-versions.sh
@@ -107,6 +107,11 @@ for VERSION in "${VERSIONS[@]}"; do
(
cd "$TEST_APP_DIR"
pnpm remove --silent @imagekit/astro 2>/dev/null || true
+ # Clear pnpm's cached extracted copies of the local tarball — pnpm keys
+ # by tarball path, not content, so an updated rebuild of the same
+ # filename would otherwise be silently reused.
+ rm -rf "$REPO_ROOT/node_modules/.pnpm/@imagekit+astro"*
+ rm -rf "$TEST_APP_DIR/node_modules/@imagekit/astro"
pnpm add --silent "$TARBALL"
pnpm remove --silent "@astrojs/node" 2>/dev/null || true
pnpm add --silent "astro@$VERSION"
diff --git a/test-app/.env.example b/test-app/.env.example
new file mode 100644
index 0000000..c4dd1bf
--- /dev/null
+++ b/test-app/.env.example
@@ -0,0 +1,5 @@
+# Server-only (used by /api/upload-auth)
+IMAGEKIT_PRIVATE_KEY=your_private_key_here
+
+# Exposed to the browser (must be PUBLIC_-prefixed in Astro)
+PUBLIC_IMAGEKIT_PUBLIC_KEY=your_public_key_here
diff --git a/test-app/astro.config.mjs b/test-app/astro.config.mjs
index 6addc61..af05aca 100644
--- a/test-app/astro.config.mjs
+++ b/test-app/astro.config.mjs
@@ -4,8 +4,17 @@ import imagekit from '@imagekit/astro/integration';
export default defineConfig({
output: 'server',
- integrations: [imagekit()],
+ integrations: [
+ imagekit({
+ urlEndpoint: 'https://ik.imagekit.io/demo/',
+ additionalEndpoints: ['https://ik.imgkit.net'],
+ }),
+ ],
adapter: node({
mode: 'standalone',
}),
+ image: {
+ layout: 'constrained',
+ domains: ['placehold.co']
+ }
});
diff --git a/test-app/e2e/__snapshot__/astro-3/images.spec.ts/Image-page-renders-correctly-1.txt b/test-app/e2e/__snapshot__/astro-3/images.spec.ts/Image-page-renders-correctly-1.txt
index 640e085..bf0bb4f 100644
--- a/test-app/e2e/__snapshot__/astro-3/images.spec.ts/Image-page-renders-correctly-1.txt
+++ b/test-app/e2e/__snapshot__/astro-3/images.spec.ts/Image-page-renders-correctly-1.txt
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/test-app/e2e/__snapshot__/astro-3/md.spec.ts/Markdown-page-renders-correctly-1.txt b/test-app/e2e/__snapshot__/astro-3/md.spec.ts/Markdown-page-renders-correctly-1.txt
new file mode 100644
index 0000000..2db57f0
--- /dev/null
+++ b/test-app/e2e/__snapshot__/astro-3/md.spec.ts/Markdown-page-renders-correctly-1.txt
@@ -0,0 +1,12 @@
+
+
ImageKit URL (primary endpoint host)
+

+
Custom IK domain (additionalEndpoints host)
+

+
Local imported asset (delegated to sharp)
+

+
Allow-listed external URL (delegated to sharp)
+

+
Non-allow-listed external URL (rendered as-is by Astro)
+

+
\ No newline at end of file
diff --git a/test-app/e2e/__snapshot__/astro-3/videos.spec.ts/Videos-page-renders-correctly-1.txt b/test-app/e2e/__snapshot__/astro-3/videos.spec.ts/Videos-page-renders-correctly-1.txt
index 6175ab0..9edc7d4 100644
--- a/test-app/e2e/__snapshot__/astro-3/videos.spec.ts/Videos-page-renders-correctly-1.txt
+++ b/test-app/e2e/__snapshot__/astro-3/videos.spec.ts/Videos-page-renders-correctly-1.txt
@@ -1 +1 @@
- Video Component Tests
Basic Video with urlEndpoint prop
Video with leading slash in src
Video with transformations
Video with all HTML attributes
Video with different urlEndpoint
Video with transformationPosition="path"
Video with custom class
Video with queryParameters
Video with chained transformations
Video without controls (autoplay, muted, loop)
Edge Case Tests
Testing extreme values and boundary conditions for video component.
Minimal dimensions (1px × 1px)
Large dimensions (4000px × 2000px)
Extreme aspect ratio (1:500)
Extreme aspect ratio (500:1)
Multiple complex transformations
transformationPosition="path"
transformationPosition="query" + queryParameters
All HTML attributes combined
\ No newline at end of file
+ Video Component Tests
Basic Video with urlEndpoint prop
Video with leading slash in src
Video with transformations
Video with different urlEndpoint
Video with transformationPosition="path"
Video with custom class
Video with queryParameters
Video with chained transformations
Video without controls (autoplay, muted, loop)
transformationPosition="path" with transformations
transformationPosition="query" + queryParameters
All HTML attributes combined
\ No newline at end of file
diff --git a/test-app/e2e/__snapshot__/astro-4/images.spec.ts/Image-page-renders-correctly-1.txt b/test-app/e2e/__snapshot__/astro-4/images.spec.ts/Image-page-renders-correctly-1.txt
index 640e085..48dc162 100644
--- a/test-app/e2e/__snapshot__/astro-4/images.spec.ts/Image-page-renders-correctly-1.txt
+++ b/test-app/e2e/__snapshot__/astro-4/images.spec.ts/Image-page-renders-correctly-1.txt
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/test-app/e2e/__snapshot__/astro-4/md.spec.ts/Markdown-page-renders-correctly-1.txt b/test-app/e2e/__snapshot__/astro-4/md.spec.ts/Markdown-page-renders-correctly-1.txt
new file mode 100644
index 0000000..1e9362f
--- /dev/null
+++ b/test-app/e2e/__snapshot__/astro-4/md.spec.ts/Markdown-page-renders-correctly-1.txt
@@ -0,0 +1,12 @@
+
+
ImageKit URL (primary endpoint host)
+

+
Custom IK domain (additionalEndpoints host)
+

+
Local imported asset (delegated to sharp)
+

+
Allow-listed external URL (delegated to sharp)
+

+
Non-allow-listed external URL (rendered as-is by Astro)
+

+
\ No newline at end of file
diff --git a/test-app/e2e/__snapshot__/astro-4/videos.spec.ts/Videos-page-renders-correctly-1.txt b/test-app/e2e/__snapshot__/astro-4/videos.spec.ts/Videos-page-renders-correctly-1.txt
index 6175ab0..9edc7d4 100644
--- a/test-app/e2e/__snapshot__/astro-4/videos.spec.ts/Videos-page-renders-correctly-1.txt
+++ b/test-app/e2e/__snapshot__/astro-4/videos.spec.ts/Videos-page-renders-correctly-1.txt
@@ -1 +1 @@
- Video Component Tests
Basic Video with urlEndpoint prop
Video with leading slash in src
Video with transformations
Video with all HTML attributes
Video with different urlEndpoint
Video with transformationPosition="path"
Video with custom class
Video with queryParameters
Video with chained transformations
Video without controls (autoplay, muted, loop)
Edge Case Tests
Testing extreme values and boundary conditions for video component.
Minimal dimensions (1px × 1px)
Large dimensions (4000px × 2000px)
Extreme aspect ratio (1:500)
Extreme aspect ratio (500:1)
Multiple complex transformations
transformationPosition="path"
transformationPosition="query" + queryParameters
All HTML attributes combined
\ No newline at end of file
+ Video Component Tests
Basic Video with urlEndpoint prop
Video with leading slash in src
Video with transformations
Video with different urlEndpoint
Video with transformationPosition="path"
Video with custom class
Video with queryParameters
Video with chained transformations
Video without controls (autoplay, muted, loop)
transformationPosition="path" with transformations
transformationPosition="query" + queryParameters
All HTML attributes combined
\ No newline at end of file
diff --git a/test-app/e2e/__snapshot__/astro-5/images.spec.ts/Image-page-renders-correctly-1.txt b/test-app/e2e/__snapshot__/astro-5/images.spec.ts/Image-page-renders-correctly-1.txt
index b863ad6..715cbd1 100644
--- a/test-app/e2e/__snapshot__/astro-5/images.spec.ts/Image-page-renders-correctly-1.txt
+++ b/test-app/e2e/__snapshot__/astro-5/images.spec.ts/Image-page-renders-correctly-1.txt
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/test-app/e2e/__snapshot__/astro-5/md.spec.ts/Markdown-page-renders-correctly-1.txt b/test-app/e2e/__snapshot__/astro-5/md.spec.ts/Markdown-page-renders-correctly-1.txt
new file mode 100644
index 0000000..af3fea4
--- /dev/null
+++ b/test-app/e2e/__snapshot__/astro-5/md.spec.ts/Markdown-page-renders-correctly-1.txt
@@ -0,0 +1,12 @@
+
+
ImageKit URL (primary endpoint host)
+

+
Custom IK domain (additionalEndpoints host)
+

+
Local imported asset (delegated to sharp)
+

+
Allow-listed external URL (delegated to sharp)
+

+
Non-allow-listed external URL (rendered as-is by Astro)
+

+
\ No newline at end of file
diff --git a/test-app/e2e/__snapshot__/astro-5/videos.spec.ts/Videos-page-renders-correctly-1.txt b/test-app/e2e/__snapshot__/astro-5/videos.spec.ts/Videos-page-renders-correctly-1.txt
index 65b0d6a..0379f50 100644
--- a/test-app/e2e/__snapshot__/astro-5/videos.spec.ts/Videos-page-renders-correctly-1.txt
+++ b/test-app/e2e/__snapshot__/astro-5/videos.spec.ts/Videos-page-renders-correctly-1.txt
@@ -1 +1 @@
- Video Component Tests
Basic Video with urlEndpoint prop
Video with leading slash in src
Video with transformations
Video with all HTML attributes
Video with different urlEndpoint
Video with transformationPosition="path"
Video with custom class
Video with queryParameters
Video with chained transformations
Video without controls (autoplay, muted, loop)
Edge Case Tests
Testing extreme values and boundary conditions for video component.
Minimal dimensions (1px × 1px)
Large dimensions (4000px × 2000px)
Extreme aspect ratio (1:500)
Extreme aspect ratio (500:1)
Multiple complex transformations
transformationPosition="path"
transformationPosition="query" + queryParameters
All HTML attributes combined
\ No newline at end of file
+ Video Component Tests
Basic Video with urlEndpoint prop
Video with leading slash in src
Video with transformations
Video with different urlEndpoint
Video with transformationPosition="path"
Video with custom class
Video with queryParameters
Video with chained transformations
Video without controls (autoplay, muted, loop)
transformationPosition="path" with transformations
transformationPosition="query" + queryParameters
All HTML attributes combined
\ No newline at end of file
diff --git a/test-app/e2e/__snapshot__/astro-6/images.spec.ts/Image-page-renders-correctly-1.txt b/test-app/e2e/__snapshot__/astro-6/images.spec.ts/Image-page-renders-correctly-1.txt
index fba8c6f..d0064fc 100644
--- a/test-app/e2e/__snapshot__/astro-6/images.spec.ts/Image-page-renders-correctly-1.txt
+++ b/test-app/e2e/__snapshot__/astro-6/images.spec.ts/Image-page-renders-correctly-1.txt
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/test-app/e2e/__snapshot__/astro-6/md.spec.ts/Markdown-page-renders-correctly-1.txt b/test-app/e2e/__snapshot__/astro-6/md.spec.ts/Markdown-page-renders-correctly-1.txt
new file mode 100644
index 0000000..d7a8b6f
--- /dev/null
+++ b/test-app/e2e/__snapshot__/astro-6/md.spec.ts/Markdown-page-renders-correctly-1.txt
@@ -0,0 +1,12 @@
+
+
ImageKit URL (primary endpoint host)
+

+
Custom IK domain (additionalEndpoints host)
+

+
Local imported asset (delegated to sharp)
+

+
Allow-listed external URL (delegated to sharp)
+

+
Non-allow-listed external URL (rendered as-is by Astro)
+

+
\ No newline at end of file
diff --git a/test-app/e2e/__snapshot__/astro-6/videos.spec.ts/Videos-page-renders-correctly-1.txt b/test-app/e2e/__snapshot__/astro-6/videos.spec.ts/Videos-page-renders-correctly-1.txt
index 65b0d6a..0379f50 100644
--- a/test-app/e2e/__snapshot__/astro-6/videos.spec.ts/Videos-page-renders-correctly-1.txt
+++ b/test-app/e2e/__snapshot__/astro-6/videos.spec.ts/Videos-page-renders-correctly-1.txt
@@ -1 +1 @@
- Video Component Tests
Basic Video with urlEndpoint prop
Video with leading slash in src
Video with transformations
Video with all HTML attributes
Video with different urlEndpoint
Video with transformationPosition="path"
Video with custom class
Video with queryParameters
Video with chained transformations
Video without controls (autoplay, muted, loop)
Edge Case Tests
Testing extreme values and boundary conditions for video component.
Minimal dimensions (1px × 1px)
Large dimensions (4000px × 2000px)
Extreme aspect ratio (1:500)
Extreme aspect ratio (500:1)
Multiple complex transformations
transformationPosition="path"
transformationPosition="query" + queryParameters
All HTML attributes combined
\ No newline at end of file
+ Video Component Tests
Basic Video with urlEndpoint prop
Video with leading slash in src
Video with transformations
Video with different urlEndpoint
Video with transformationPosition="path"
Video with custom class
Video with queryParameters
Video with chained transformations
Video without controls (autoplay, muted, loop)
transformationPosition="path" with transformations
transformationPosition="query" + queryParameters
All HTML attributes combined
\ No newline at end of file
diff --git a/test-app/e2e/fixtures.ts b/test-app/e2e/fixtures.ts
deleted file mode 100644
index ddf0efe..0000000
--- a/test-app/e2e/fixtures.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { test as base, expect } from '@playwright/test';
-import { createRequire } from 'module';
-export { expect };
-
-function getInstalledAstroVersion(): string {
- try {
- const require = createRequire(import.meta.url);
- const astroPkg = require('astro/package.json');
- return astroPkg.version;
- } catch {
- return 'latest';
- }
-}
-
-export type AstroVersionFixture = {
- astroVersion: string;
-};
-
-export const test = base.extend({
- // eslint-disable-next-line no-empty-pattern
- astroVersion: async ({}, use) => {
- const version = getInstalledAstroVersion();
- await use(version);
- },
-});
-
-export function parseVersion(version: string): [number, number, number] {
- const parts = version.split('.').map((v) => parseInt(v, 10) || 0);
- if (parts.length === 1) {
- return [parts[0], 0, 0];
- }
- if (parts.length === 2) {
- return [parts[0], parts[1], 0];
- }
- return parts.slice(0, 3) as [number, number, number];
-}
-
-export function isVersionBelow(
- currentVersion: string,
- minVersion: string
-): boolean {
- const current = parseVersion(currentVersion);
- const min = parseVersion(minVersion);
-
- for (let i = 0; i < 3; i++) {
- if (current[i] < min[i]) return true;
- if (current[i] > min[i]) return false;
- }
- return false;
-}
-
-export function skipIfAstroVersionBelow(
- currentVersion: string,
- minVersion: string
-): boolean {
- return isVersionBelow(currentVersion, minVersion);
-}
diff --git a/test-app/e2e/images.spec.ts b/test-app/e2e/images.spec.ts
index 1f724a5..422c16c 100644
--- a/test-app/e2e/images.spec.ts
+++ b/test-app/e2e/images.spec.ts
@@ -1,649 +1,17 @@
-import { test, expect, skipIfAstroVersionBelow } from "./fixtures";
+import { test, expect } from "@playwright/test";
test("Image page renders correctly", async ({ page }) => {
await page.goto("/images");
- // Scroll to the bottom of the page to trigger lazy loading
+ // Scroll to the bottom to trigger lazy loading
await page.evaluate(() => {
window.scrollTo(0, document.body.scrollHeight);
});
- await page.waitForTimeout(2000); // Wait for images to load
+ await page.waitForTimeout(2000);
- // Locate the container element
- const container = page.locator('.container');
+ const container = page.locator(".container");
+ const outputHtml = await container.evaluate((el) => el.outerHTML);
- // Grab the entire HTML from the element
- const outputHtml = await container.evaluate(el => el.outerHTML);
-
- // Compare against a stored snapshot
expect(outputHtml).toMatchSnapshot();
});
-
-test("Image renders with correct class", async ({ page }) => {
- await page.goto("/images");
-
- // Check that Image has the correct base class
- const images = page.locator('img.astro-imagekit-image');
- const count = await images.count();
- expect(count).toBeGreaterThan(0);
-});
-
-test("Image with custom class has both classes", async ({ page }) => {
- await page.goto("/images");
-
- // Check for image with custom class
- const imageWithCustomClass = page.locator('img.astro-imagekit-image.custom-class');
- await expect(imageWithCustomClass).toBeVisible();
-});
-
-test("Image generates srcset for responsive images", async ({ page }) => {
- await page.goto("/images");
-
- // Get the first responsive image (example 1)
- const responsiveImage = page.locator('.example').nth(0).locator('img');
- const srcset = await responsiveImage.getAttribute('srcset');
-
- // Responsive images should have srcset
- expect(srcset).toBeTruthy();
- expect(srcset).toContain('ik.imagekit.io');
-});
-
-test("Image non-responsive does not generate srcset", async ({ page }) => {
- await page.goto("/images");
-
- // Example 7 is non-responsive
- const nonResponsiveImage = page.locator('.example').nth(6).locator('img');
- const srcset = await nonResponsiveImage.getAttribute('srcset');
-
- // Non-responsive images should not have srcset
- expect(srcset).toBeFalsy();
-});
-
-test("Image with transformations generates correct URL", async ({ page }) => {
- await page.goto("/images");
-
- // Example 3 has transformation
- const transformedImage = page.locator('.example').nth(2).locator('img');
- const src = await transformedImage.getAttribute('src');
-
- // src should contain transformation parameters
- expect(src).toContain('ik.imagekit.io');
- expect(src).toContain('tr=');
-});
-
-test("Image with queryParameters includes them in URL", async ({ page }) => {
- await page.goto("/images");
-
- // Example 4 has queryParameters
- const imageWithQuery = page.locator('.example').nth(3).locator('img');
- const src = await imageWithQuery.getAttribute('src');
-
- // src should contain query parameter
- expect(src).toContain('version=v1');
-});
-
-test("Image with loading=eager has correct attribute", async ({ page }) => {
- await page.goto("/images");
-
- // Example 9 has loading="eager"
- const eagerImage = page.locator('.example').nth(8).locator('img');
- const loading = await eagerImage.getAttribute('loading');
-
- expect(loading).toBe('eager');
-});
-
-test("Image with data attributes preserves them", async ({ page }) => {
- await page.goto("/images");
-
- // Example 14 has data attributes
- const imageWithData = page.locator('.example').nth(13).locator('img');
- const testId = await imageWithData.getAttribute('data-testid');
- const customAttr = await imageWithData.getAttribute('data-custom');
-
- expect(testId).toBe('test-image');
- expect(customAttr).toBe('value');
-});
-
-test("Image with transformationPosition=path uses path transformation", async ({ page }) => {
- await page.goto("/images");
-
- // Example 11 has transformationPosition="path" with transformations
- const pathImage = page.locator('.example').nth(10).locator('img');
- const src = await pathImage.getAttribute('src');
-
- // Path transformations should have tr: in the path, not ?tr=
- expect(src).toContain('/tr:');
-});
-
-// ============================================
-// ASTRO IMAGE COMPONENT PROPS TESTS
-// ============================================
-
-test("Image with decoding=sync has correct attribute", async ({ page }) => {
- await page.goto("/images");
-
- // Example 19 has decoding="sync"
- const syncDecodingImage = page.locator('.example').nth(18).locator('img');
- const decoding = await syncDecodingImage.getAttribute('decoding');
-
- expect(decoding).toBe('sync');
-});
-
-test("Image with fetchpriority=high has correct attribute", async ({ page }) => {
- await page.goto("/images");
-
- // Example 20 has fetchpriority="high"
- const highPriorityImage = page.locator('.example').nth(19).locator('img');
- const fetchpriority = await highPriorityImage.getAttribute('fetchpriority');
-
- expect(fetchpriority).toBe('high');
-});
-
-test("Image with densities generates density-based srcset", async ({ page }) => {
- await page.goto("/images");
-
- // Example 21 has densities=[1.5, 2]
- const densitiesImage = page.locator('.example').nth(20).locator('img');
- const srcset = await densitiesImage.getAttribute('srcset');
-
- // Should contain density descriptors (1.5x, 2x)
- expect(srcset).toBeTruthy();
- if (srcset) {
- expect(srcset).toMatch(/\d+(\.\d+)?x/);
- }
-});
-
-test("Image with widths generates width-based srcset", async ({ page }) => {
- await page.goto("/images");
-
- // Example 22 has widths=[240, 540, 720]
- const widthsImage = page.locator('.example').nth(21).locator('img');
- const srcset = await widthsImage.getAttribute('srcset');
- const sizes = await widthsImage.getAttribute('sizes');
-
- // Should contain width descriptors (240w, 540w, 720w)
- expect(srcset).toBeTruthy();
- expect(sizes).toBeTruthy();
- if (srcset) {
- expect(srcset).toMatch(/\d+w/);
- }
-});
-
-test("Image with layout=constrained has correct data attribute", async ({ page, astroVersion }) => {
- test.skip(skipIfAstroVersionBelow(astroVersion, '5.10.0'), 'Requires Astro 5.10.0+ for stable responsive images');
- await page.goto("/images");
-
- // Example 23 has layout="constrained"
- const constrainedImage = page.locator('.example').nth(22).locator('img');
- const dataAstroImage = await constrainedImage.getAttribute('data-astro-image');
-
- expect(dataAstroImage).toBe('constrained');
-});
-
-test("Image with layout=full-width has correct data attribute", async ({ page, astroVersion }) => {
- test.skip(skipIfAstroVersionBelow(astroVersion, '5.10.0'), 'Requires Astro 5.10.0+ for stable responsive images');
- await page.goto("/images");
-
- // Example 24 has layout="full-width"
- const fullWidthImage = page.locator('.example').nth(23).locator('img');
- const dataAstroImage = await fullWidthImage.getAttribute('data-astro-image');
-
- expect(dataAstroImage).toBe('full-width');
-});
-
-test("Image with layout=fixed has correct data attribute", async ({ page, astroVersion }) => {
- test.skip(skipIfAstroVersionBelow(astroVersion, '5.10.0'), 'Requires Astro 5.10.0+ for stable responsive images');
- await page.goto("/images");
-
- // Example 25 has layout="fixed"
- const fixedImage = page.locator('.example').nth(24).locator('img');
- const dataAstroImage = await fixedImage.getAttribute('data-astro-image');
-
- expect(dataAstroImage).toBe('fixed');
-});
-
-test("Image with fit=contain has correct style", async ({ page, astroVersion }) => {
- test.skip(skipIfAstroVersionBelow(astroVersion, '5.10.0'), 'Requires Astro 5.10.0+ for stable responsive images');
- await page.goto("/images");
-
- // Example 26 has fit="contain"
- const containFitImage = page.locator('.example').nth(25).locator('img');
- const style = await containFitImage.getAttribute('style');
-
- // Should have object-fit CSS property set to contain
- expect(style).toContain('object-fit');
- expect(style).toContain('contain');
-});
-
-test("Image with position prop has correct style", async ({ page, astroVersion }) => {
- test.skip(skipIfAstroVersionBelow(astroVersion, '5.10.0'), 'Requires Astro 5.10.0+ for stable responsive images');
- await page.goto("/images");
-
- // Example 27 has position="top left"
- const positionImage = page.locator('.example').nth(26).locator('img');
- const style = await positionImage.getAttribute('style');
-
- // Should have object-position CSS property set to top left
- expect(style).toContain('object-position');
- expect(style).toContain('top left');
-});
-
-test("Image with priority has eager loading attributes", async ({ page, astroVersion }) => {
- test.skip(skipIfAstroVersionBelow(astroVersion, '5.10.0'), 'Requires Astro 5.10.0+ for stable responsive images');
- await page.goto("/images");
-
- // Example 28 has priority flag
- const priorityImage = page.locator('.example').nth(27).locator('img');
- const loading = await priorityImage.getAttribute('loading');
- const decoding = await priorityImage.getAttribute('decoding');
- const fetchpriority = await priorityImage.getAttribute('fetchpriority');
-
- // priority should set loading="eager", decoding="sync", fetchpriority="high"
- expect(loading).toBe('eager');
- expect(decoding).toBe('sync');
- expect(fetchpriority).toBe('high');
-});
-
-test("Image with inline style preserves style attribute", async ({ page }) => {
- await page.goto("/images");
-
- // Example 29 has inline style
- const styledImage = page.locator('.example').nth(28).locator('img');
- const style = await styledImage.getAttribute('style');
-
- expect(style).toContain('border');
- expect(style).toContain('border-radius');
-});
-
-test("Image with id attribute preserves it", async ({ page }) => {
- await page.goto("/images");
-
- // Example 30 has id attribute
- const imageWithId = page.locator('#my-image-id');
- await expect(imageWithId).toBeVisible();
-});
-
-// ============================================
-// PROPERTY CONFLICT TESTS (Examples 31-35)
-// ============================================
-
-test.describe('Property Conflict Tests', () => {
- test('CONFLICT: densities + layout should handle gracefully', async ({ page, astroVersion }) => {
- test.skip(skipIfAstroVersionBelow(astroVersion, '5.10.0'), 'Requires Astro 5.10.0+ for stable responsive images');
- await page.goto("/images");
-
- // Example 31: densities + layout conflict
- const conflictImage = page.locator('[data-test-id="conflict-densities-layout"]').locator('img');
-
- // Image should still render
- await expect(conflictImage).toBeVisible();
-
- // Per Astro docs, when layout is set, densities should be ignored
- // The image should use layout-based srcset, not density-based
- const dataAstroImage = await conflictImage.getAttribute('data-astro-image');
- expect(dataAstroImage).toBe('constrained');
- });
-
- test('CONFLICT: densities + widths should only use one', async ({ page }) => {
- await page.goto("/images");
-
- // Example 32: densities + widths conflict
- const conflictImage = page.locator('[data-test-id="conflict-densities-widths"]').locator('img');
-
- // Image should render
- await expect(conflictImage).toBeVisible();
-
- // Should have srcset (either from widths or densities, not both)
- const srcset = await conflictImage.getAttribute('srcset');
- expect(srcset).toBeTruthy();
- });
-
- test('CONFLICT: widths without sizes should handle gracefully', async ({ page }) => {
- await page.goto("/images");
-
- // Example 33: widths without sizes
- const conflictImage = page.locator('[data-test-id="conflict-widths-no-sizes"]').locator('img');
-
- // Image should render
- await expect(conflictImage).toBeVisible();
-
- // sizes might be auto-generated or srcset might not be present
- // The important thing is no errors occur
- const alt = await conflictImage.getAttribute('alt');
- expect(alt).toBeTruthy();
- });
-
- test('CONFLICT: layout=fixed + widths should not generate width variants', async ({ page, astroVersion }) => {
- test.skip(skipIfAstroVersionBelow(astroVersion, '5.10.0'), 'Requires Astro 5.10.0+ for stable responsive images');
- await page.goto("/images");
-
- // Example 34: layout=fixed + widths
- const conflictImage = page.locator('[data-test-id="conflict-fixed-widths"]').locator('img');
-
- // Image should render
- await expect(conflictImage).toBeVisible();
-
- // Should have data-astro-image="fixed"
- const dataAstroImage = await conflictImage.getAttribute('data-astro-image');
- expect(dataAstroImage).toBe('fixed');
- });
-
- test('CONFLICT: layout=none + responsive should disable responsiveness', async ({ page }) => {
- await page.goto("/images");
-
- // Example 35: layout=none + responsive
- const conflictImage = page.locator('[data-test-id="conflict-none-layout-responsive"]').locator('img');
-
- // Image should render
- await expect(conflictImage).toBeVisible();
-
- // layout="none" should not generate srcset or data-astro-image attribute
- const dataAstroImage = await conflictImage.getAttribute('data-astro-image');
- // With layout=none, data-astro-image might not be present or be null
- expect(dataAstroImage).toBeNull();
- });
-});
-
-// ============================================
-// VALID PROPERTY COMBINATION TESTS (Examples 36-40)
-// ============================================
-
-test.describe('Valid Property Combinations', () => {
- test('VALID: widths + sizes should generate proper srcset', async ({ page }) => {
- await page.goto("/images");
-
- // Example 36: widths + sizes (valid combination)
- const validImage = page.locator('[data-test-id="valid-widths-sizes"]').locator('img');
-
- await expect(validImage).toBeVisible();
-
- const srcset = await validImage.getAttribute('srcset');
- const sizes = await validImage.getAttribute('sizes');
-
- // Both should exist
- expect(srcset).toBeTruthy();
- expect(sizes).toBeTruthy();
-
- // srcset should contain width descriptors
- if (srcset) {
- expect(srcset).toMatch(/\d+w/);
- }
- });
-
- test('VALID: densities alone should generate density-based srcset', async ({ page }) => {
- await page.goto("/images");
-
- // Example 37: densities alone (valid)
- const validImage = page.locator('[data-test-id="valid-densities-alone"]').locator('img');
-
- await expect(validImage).toBeVisible();
-
- const srcset = await validImage.getAttribute('srcset');
-
- // Should have density descriptors
- expect(srcset).toBeTruthy();
- if (srcset) {
- expect(srcset).toMatch(/\d+(\.\d+)?x/);
- }
- });
-
- test('VALID: layout + format + quality should work together', async ({ page, astroVersion }) => {
- test.skip(skipIfAstroVersionBelow(astroVersion, '5.10.0'), 'Requires Astro 5.10.0+ for stable responsive images');
- await page.goto("/images");
-
- // Example 38: layout + format + quality (valid)
- const validImage = page.locator('[data-test-id="valid-layout-format-quality"]').locator('img');
-
- await expect(validImage).toBeVisible();
-
- // Should have constrained layout
- const dataAstroImage = await validImage.getAttribute('data-astro-image');
- expect(dataAstroImage).toBe('constrained');
-
- // URL should contain format and quality parameters
- const src = await validImage.getAttribute('src');
- expect(src).toContain('ik.imagekit.io');
- });
-
- test('VALID: layout=full-width with responsive props', async ({ page, astroVersion }) => {
- test.skip(skipIfAstroVersionBelow(astroVersion, '5.10.0'), 'Requires Astro 5.10.0+ for stable responsive images');
- await page.goto("/images");
-
- // Example 39: layout=full-width
- const validImage = page.locator('[data-test-id="valid-fullwidth-responsive"]').locator('img');
-
- await expect(validImage).toBeVisible();
-
- // Should have full-width layout
- const dataAstroImage = await validImage.getAttribute('data-astro-image');
- expect(dataAstroImage).toBe('full-width');
-
- // Should generate srcset for responsive behavior
- const srcset = await validImage.getAttribute('srcset');
- expect(srcset).toBeTruthy();
- });
-
- test('VALID: responsive=true + ImageKit breakpoints', async ({ page }) => {
- await page.goto("/images");
-
- // Example 40: responsive + breakpoints (valid)
- const validImage = page.locator('[data-test-id="valid-responsive-breakpoints"]').locator('img');
-
- await expect(validImage).toBeVisible();
-
- // Should have srcset due to responsive=true
- const srcset = await validImage.getAttribute('srcset');
- expect(srcset).toBeTruthy();
- });
-});
-
-// ============================================
-// EDGE CASE TESTS (Examples 41-57)
-// ============================================
-
-test.describe('Edge Case Tests - Dimensions', () => {
- test('Minimal dimensions (1px × 1px)', async ({ page }) => {
- await page.goto("/images");
-
- const edgeImage = page.locator('[data-test-id="edge-minimal-dimensions"]').locator('img');
-
- await expect(edgeImage).toBeVisible();
-
- const width = await edgeImage.getAttribute('width');
- expect(width).toBe('1');
- });
-
- test('Extreme aspect ratio (1:1000)', async ({ page }) => {
- await page.goto("/images");
-
- const edgeImage = page.locator('[data-test-id="edge-extreme-ratio-tall"]').locator('img');
-
- await expect(edgeImage).toBeVisible();
-
- const height = await edgeImage.getAttribute('height');
- expect(height).toBe('10000');
- });
-
- test('Extreme aspect ratio (1000:1)', async ({ page }) => {
- await page.goto("/images");
-
- const edgeImage = page.locator('[data-test-id="edge-extreme-ratio-wide"]').locator('img');
-
- await expect(edgeImage).toBeVisible();
-
- const width = await edgeImage.getAttribute('width');
- expect(width).toBe('10000');
- });
-
- test('Large width (5000px)', async ({ page }) => {
- await page.goto("/images");
-
- const edgeImage = page.locator('[data-test-id="edge-large-width"]').locator('img');
-
- await expect(edgeImage).toBeVisible();
-
- const width = await edgeImage.getAttribute('width');
- expect(width).toBe('5000');
- });
-});
-
-test.describe('Edge Case Tests - Quality', () => {
- test('Quality = 0 (minimum)', async ({ page }) => {
- await page.goto("/images");
-
- const edgeImage = page.locator('[data-test-id="edge-quality-0"]').locator('img');
-
- await expect(edgeImage).toBeVisible();
-
- // Should render without error even with minimum quality
- const src = await edgeImage.getAttribute('src');
- expect(src).toBeTruthy();
- });
-
- test('Quality = 100 (maximum)', async ({ page }) => {
- await page.goto("/images");
-
- const edgeImage = page.locator('[data-test-id="edge-quality-100"]').locator('img');
-
- await expect(edgeImage).toBeVisible();
-
- // Should render without error even with maximum quality
- const src = await edgeImage.getAttribute('src');
- expect(src).toBeTruthy();
- });
-
- test('Quality preset = "low"', async ({ page }) => {
- await page.goto("/images");
-
- const edgeImage = page.locator('[data-test-id="edge-quality-preset-low"]').locator('img');
-
- await expect(edgeImage).toBeVisible();
-
- const src = await edgeImage.getAttribute('src');
- expect(src).toContain('ik.imagekit.io');
- });
-
- test('Quality preset = "mid"', async ({ page }) => {
- await page.goto("/images");
-
- const edgeImage = page.locator('[data-test-id="edge-quality-preset-mid"]').locator('img');
-
- await expect(edgeImage).toBeVisible();
-
- const src = await edgeImage.getAttribute('src');
- expect(src).toContain('ik.imagekit.io');
- });
-
- test('Quality preset = "max"', async ({ page }) => {
- await page.goto("/images");
-
- const edgeImage = page.locator('[data-test-id="edge-quality-preset-max"]').locator('img');
-
- await expect(edgeImage).toBeVisible();
-
- const src = await edgeImage.getAttribute('src');
- expect(src).toContain('ik.imagekit.io');
- });
-});
-
-test.describe('Edge Case Tests - Formats', () => {
- test('Format = webp', async ({ page }) => {
- await page.goto("/images");
-
- const edgeImage = page.locator('[data-test-id="edge-format-webp"]').locator('img');
-
- await expect(edgeImage).toBeVisible();
-
- const src = await edgeImage.getAttribute('src');
- expect(src).toBeTruthy();
- });
-
- test('Format = avif', async ({ page }) => {
- await page.goto("/images");
-
- const edgeImage = page.locator('[data-test-id="edge-format-avif"]').locator('img');
-
- await expect(edgeImage).toBeVisible();
-
- const src = await edgeImage.getAttribute('src');
- expect(src).toBeTruthy();
- });
-
- test('Format = svg', async ({ page }) => {
- await page.goto("/images");
-
- const edgeImage = page.locator('[data-test-id="edge-format-svg"]').locator('img');
-
- await expect(edgeImage).toBeVisible();
-
- const src = await edgeImage.getAttribute('src');
- expect(src).toBeTruthy();
- });
-});
-
-test.describe('Edge Case Tests - Transformations', () => {
- test('Multiple complex transformations', async ({ page }) => {
- await page.goto("/images");
-
- const edgeImage = page.locator('[data-test-id="edge-complex-transformations"]').locator('img');
-
- await expect(edgeImage).toBeVisible();
-
- // Should contain transformation parameters in URL
- const src = await edgeImage.getAttribute('src');
- expect(src).toContain('ik.imagekit.io');
- expect(src).toContain('tr=');
- });
-
- test('transformationPosition="path"', async ({ page }) => {
- await page.goto("/images");
-
- const edgeImage = page.locator('[data-test-id="edge-transform-position-path"]').locator('img');
-
- await expect(edgeImage).toBeVisible();
-
- // Path transformations should use /tr: syntax
- const src = await edgeImage.getAttribute('src');
- expect(src).toContain('/tr:');
- });
-
- test('transformationPosition="query"', async ({ page }) => {
- await page.goto("/images");
-
- const edgeImage = page.locator('[data-test-id="edge-transform-position-query"]').locator('img');
-
- await expect(edgeImage).toBeVisible();
-
- // Query transformations should use ?tr= syntax or query params
- const src = await edgeImage.getAttribute('src');
- expect(src).toContain('?');
- });
-});
-
-test.describe('Edge Case Tests - Responsive Breakpoints', () => {
- test('Extreme device breakpoints', async ({ page }) => {
- await page.goto("/images");
-
- const edgeImage = page.locator('[data-test-id="edge-device-breakpoints"]').locator('img');
-
- await expect(edgeImage).toBeVisible();
-
- // Should generate srcset with extreme breakpoints
- const srcset = await edgeImage.getAttribute('srcset');
- expect(srcset).toBeTruthy();
- });
-
- test('Extreme image breakpoints', async ({ page }) => {
- await page.goto("/images");
-
- const edgeImage = page.locator('[data-test-id="edge-image-breakpoints"]').locator('img');
-
- await expect(edgeImage).toBeVisible();
-
- // Should generate srcset with extreme breakpoints
- const srcset = await edgeImage.getAttribute('srcset');
- expect(srcset).toBeTruthy();
- });
-});
-
diff --git a/test-app/e2e/md.spec.ts b/test-app/e2e/md.spec.ts
new file mode 100644
index 0000000..3d2d938
--- /dev/null
+++ b/test-app/e2e/md.spec.ts
@@ -0,0 +1,16 @@
+import { test, expect } from "@playwright/test";
+
+test("Markdown page renders correctly", async ({ page }) => {
+ await page.goto("/md");
+
+ await page.evaluate(() => {
+ window.scrollTo(0, document.body.scrollHeight);
+ });
+
+ await page.waitForTimeout(2000);
+
+ const container = page.locator(".container");
+ const outputHtml = await container.evaluate((el) => el.outerHTML);
+
+ expect(outputHtml).toMatchSnapshot();
+});
diff --git a/test-app/e2e/og.spec.ts b/test-app/e2e/og.spec.ts
new file mode 100644
index 0000000..d8ab253
--- /dev/null
+++ b/test-app/e2e/og.spec.ts
@@ -0,0 +1,61 @@
+import { test, expect } from "@playwright/test";
+
+test("OG page emits expected meta tags", async ({ page }) => {
+ await page.goto("/og");
+
+ const IK_URL = "https://ik.imagekit.io/demo/default-image.jpg?tr=w-1200,h-630";
+ const ALT = "Default ImageKit demo image";
+
+ await expect(page.locator('meta[property="og:title"]')).toHaveAttribute(
+ "content",
+ "Hello from @imagekit/astro",
+ );
+ await expect(page.locator('meta[property="og:type"]')).toHaveAttribute(
+ "content",
+ "article",
+ );
+ await expect(page.locator('meta[property="og:image"]')).toHaveAttribute(
+ "content",
+ IK_URL,
+ );
+ await expect(
+ page.locator('meta[property="og:image:secure_url"]'),
+ ).toHaveAttribute("content", IK_URL);
+ await expect(page.locator('meta[property="og:image:width"]')).toHaveAttribute(
+ "content",
+ "1200",
+ );
+ await expect(page.locator('meta[property="og:image:height"]')).toHaveAttribute(
+ "content",
+ "630",
+ );
+ await expect(page.locator('meta[property="og:image:alt"]')).toHaveAttribute(
+ "content",
+ ALT,
+ );
+
+ await expect(page.locator('meta[name="twitter:card"]')).toHaveAttribute(
+ "content",
+ "summary_large_image",
+ );
+ await expect(page.locator('meta[name="twitter:title"]')).toHaveAttribute(
+ "content",
+ "Hello from @imagekit/astro",
+ );
+ await expect(page.locator('meta[name="twitter:image"]')).toHaveAttribute(
+ "content",
+ IK_URL,
+ );
+ await expect(page.locator('meta[name="twitter:image:alt"]')).toHaveAttribute(
+ "content",
+ ALT,
+ );
+
+ // Helper-built escape hatch URL — full match
+ await expect(
+ page.locator('meta[property="og:image:secondary"]'),
+ ).toHaveAttribute(
+ "content",
+ "https://ik.imagekit.io/demo/default-image.jpg?tr=bl-5:w-1200,h-630",
+ );
+});
diff --git a/test-app/e2e/videos.spec.ts b/test-app/e2e/videos.spec.ts
index 26cdf82..a552be0 100644
--- a/test-app/e2e/videos.spec.ts
+++ b/test-app/e2e/videos.spec.ts
@@ -3,132 +3,15 @@ import { test, expect } from "@playwright/test";
test("Videos page renders correctly", async ({ page }) => {
await page.goto("/videos");
- // Scroll to the bottom of the page
+ // Scroll to the bottom to trigger lazy loading
await page.evaluate(() => {
window.scrollTo(0, document.body.scrollHeight);
});
- await page.waitForTimeout(2000); // Wait for videos to load
+ await page.waitForTimeout(2000);
- // Locate the container element
- const container = page.locator('.container');
+ const container = page.locator(".container");
+ const outputHtml = await container.evaluate((el) => el.outerHTML);
- // Grab the entire HTML from the element
- const outputHtml = await container.evaluate(el => el.outerHTML);
-
- // Compare against a stored snapshot
expect(outputHtml).toMatchSnapshot();
});
-
-// ============================================
-// EDGE CASE TESTS FOR VIDEO COMPONENT
-// ============================================
-
-test.describe('Video Edge Case Tests - Dimensions', () => {
- test('Minimal dimensions (1px × 1px)', async ({ page }) => {
- await page.goto("/videos");
-
- const edgeVideo = page.locator('[data-test-id="video-edge-minimal"]').locator('video');
-
- await expect(edgeVideo).toBeVisible();
-
- const width = await edgeVideo.getAttribute('width');
- expect(width).toBe('1');
- });
-
- test('Large dimensions (4000px × 2000px)', async ({ page }) => {
- await page.goto("/videos");
-
- const edgeVideo = page.locator('[data-test-id="video-edge-large"]').locator('video');
-
- await expect(edgeVideo).toBeVisible();
-
- const width = await edgeVideo.getAttribute('width');
- expect(width).toBe('4000');
- });
-
- test('Extreme aspect ratio (1:500)', async ({ page }) => {
- await page.goto("/videos");
-
- const edgeVideo = page.locator('[data-test-id="video-edge-ratio-tall"]').locator('video');
-
- await expect(edgeVideo).toBeVisible();
-
- const height = await edgeVideo.getAttribute('height');
- expect(height).toBe('5000');
- });
-
- test('Extreme aspect ratio (500:1)', async ({ page }) => {
- await page.goto("/videos");
-
- const edgeVideo = page.locator('[data-test-id="video-edge-ratio-wide"]').locator('video');
-
- await expect(edgeVideo).toBeVisible();
-
- const width = await edgeVideo.getAttribute('width');
- expect(width).toBe('5000');
- });
-});
-
-test.describe('Video Edge Case Tests - Transformations', () => {
- test('Multiple complex transformations', async ({ page }) => {
- await page.goto("/videos");
-
- const edgeVideo = page.locator('[data-test-id="video-edge-complex-transformations"]').locator('video');
-
- await expect(edgeVideo).toBeVisible();
-
- // Should contain transformation parameters
- const src = await edgeVideo.getAttribute('src');
- expect(src).toContain('ik.imagekit.io');
- expect(src).toContain('tr=');
- });
-
- test('transformationPosition="path"', async ({ page }) => {
- await page.goto("/videos");
-
- const edgeVideo = page.locator('[data-test-id="video-edge-transform-path"]').locator('video');
-
- await expect(edgeVideo).toBeVisible();
-
- // Path transformations should use /tr: syntax
- const src = await edgeVideo.getAttribute('src');
- expect(src).toContain('/tr:');
- });
-
- test('transformationPosition="query"', async ({ page }) => {
- await page.goto("/videos");
-
- const edgeVideo = page.locator('[data-test-id="video-edge-transform-query"]').locator('video');
-
- await expect(edgeVideo).toBeVisible();
-
- // Query transformations should use query params
- const src = await edgeVideo.getAttribute('src');
- expect(src).toContain('?');
- });
-});
-
-test.describe('Video Edge Case Tests - Attributes', () => {
- test('All HTML attributes combined', async ({ page }) => {
- await page.goto("/videos");
-
- const edgeVideo = page.locator('[data-test-id="video-edge-all-attributes"]').locator('video');
-
- await expect(edgeVideo).toBeVisible();
-
- // Check for all attributes
- const controls = await edgeVideo.getAttribute('controls');
- const autoplay = await edgeVideo.getAttribute('autoplay');
- const loop = await edgeVideo.getAttribute('loop');
- const muted = await edgeVideo.getAttribute('muted');
- const preload = await edgeVideo.getAttribute('preload');
-
- expect(controls).not.toBeNull();
- expect(autoplay).not.toBeNull();
- expect(loop).not.toBeNull();
- expect(muted).not.toBeNull();
- expect(preload).toBe('metadata');
- });
-});
-
diff --git a/test-app/package.json b/test-app/package.json
index 9802f29..16ca023 100644
--- a/test-app/package.json
+++ b/test-app/package.json
@@ -13,6 +13,7 @@
"dependencies": {
"@astrojs/node": "^9.5.5",
"@imagekit/astro": "workspace:*",
+ "@imagekit/javascript": "^5.1.0",
"astro": "^5.18.1"
},
"devDependencies": {
diff --git a/test-app/playwright.config.ts b/test-app/playwright.config.ts
index 1e3a348..8947664 100644
--- a/test-app/playwright.config.ts
+++ b/test-app/playwright.config.ts
@@ -1,6 +1,5 @@
import { defineConfig, devices } from "@playwright/test";
import path from "path";
-import "./e2e/fixtures";
// Use process.env.PORT by default and fallback to port 4321
const PORT = process.env.PORT || 4321;
diff --git a/test-app/src/assets/hero.jpg b/test-app/src/assets/hero.jpg
new file mode 100644
index 0000000..56fb86b
Binary files /dev/null and b/test-app/src/assets/hero.jpg differ
diff --git a/test-app/src/pages/api/upload-auth.ts b/test-app/src/pages/api/upload-auth.ts
index 9aa6737..41d5c1a 100644
--- a/test-app/src/pages/api/upload-auth.ts
+++ b/test-app/src/pages/api/upload-auth.ts
@@ -7,7 +7,7 @@ export const GET: APIRoute = async () => {
try {
const authParams = getUploadAuthParams({
privateKey: import.meta.env.IMAGEKIT_PRIVATE_KEY,
- publicKey: import.meta.env.IMAGEKIT_PUBLIC_KEY,
+ publicKey: import.meta.env.PUBLIC_IMAGEKIT_PUBLIC_KEY,
});
return new Response(JSON.stringify(authParams), {
status: 200,
@@ -20,7 +20,7 @@ export const GET: APIRoute = async () => {
token: 'dummy-token',
signature: 'dummy-signature',
expire: Math.floor(Date.now() / 1000) + 30 * 60, // 30 minutes from now
- publicKey: import.meta.env.IMAGEKIT_PUBLIC_KEY || 'dummy-public-key',
+ publicKey: import.meta.env.PUBLIC_IMAGEKIT_PUBLIC_KEY || 'dummy-public-key',
};
console.warn('Using dummy auth params for CI environment');
return new Response(JSON.stringify(dummyAuthParams), {
diff --git a/test-app/src/pages/images.astro b/test-app/src/pages/images.astro
index ca480c5..10c0f99 100644
--- a/test-app/src/pages/images.astro
+++ b/test-app/src/pages/images.astro
@@ -1,5 +1,6 @@
---
-import { Image } from '@imagekit/astro';
+import { Image } from 'astro:assets';
+import heroImage from '../assets/hero.jpg';
---
@@ -35,15 +36,28 @@ import { Image } from '@imagekit/astro';
Image Component Tests
-
Image uses Astro's built-in Image component for optimized image delivery with ImageKit transformations.
+
Image uses Astro's built-in Image component for optimized image delivery with ImageKit transformations. The urlEndpoint is configured globally in astro.config.mjs; cases below only pass it explicitly when overriding it.
-
+
-
1. Basic Image with urlEndpoint prop
+ Basic image (urlEndpoint from integration config)
+
+
+
+
+
urlEndpoint prop override
+
@@ -51,9 +65,8 @@ import { Image } from '@imagekit/astro';
-
2. Image with leading slash in src
+ Leading slash in src
+
+
+
Absolute URL src (urlEndpoint ignored, transformations still applied)
+
+
+
-
3. Image with transformation
+
Transformation prop
-
4. Image with queryParameters
+
queryParameters prop
-
5. Responsive image with sizes
+
Responsive image with sizes
-
6. Responsive image with fixed sizes (300px)
+ Responsive image with fixed sizes (300px)
-
-
-
7. Non-responsive image (responsive=false)
-
-
-
-
8. Image with custom class
+
Custom class
-
9. Image with loading="eager"
+
loading="eager"
-
10. Image with transformationPosition="path"
+
transformationPosition="path"
-
11. Image with transformationPosition="path" + custom transformations
+ transformationPosition="path" + custom transformations
-
-
-
12. Image with custom deviceBreakpoints
-
-
-
-
-
-
13. Image with custom imageBreakpoints
-
-
-
-
14. Image with data attributes
+
Data attributes
-
15. Image with multiple chained transformations
+
Multiple chained transformations
-
16. Image with format="avif"
+
format="avif" ignored
-
17. Image with quality="high"
+
quality="high"
-
18. Image with quality=50
+
quality=50
-
19. Image with decoding="sync"
+
decoding="sync"
-
20. Image with fetchpriority="high"
+
fetchpriority="high"
-
21. Image with densities=[1.5, 2]
+
densities=[1.5, 2] (requires layout="none" to override config default)
@@ -323,9 +293,8 @@ import { Image } from '@imagekit/astro';
-
22. Image with widths=[240, 540, 720]
+
widths=[240, 540, 720]
-
23. Image with layout="constrained"
+
layout="constrained"
-
24. Image with layout="full-width"
+
layout="full-width"
-
25. Image with layout="fixed"
+ layout="fixed"
-
-
-
26. Image with fit="contain"
-
-
-
-
+
-
27. Image with position="top left"
+
layout="none" (responsive off)
@@ -404,9 +354,8 @@ import { Image } from '@imagekit/astro';
-
28. Image with priority (eager loading shorthand)
+
priority (eager loading shorthand)
-
29. Image with inline style
+
Inline style
-
30. Image with id attribute
+ id attribute
-
- Property Conflict Tests (Invalid Combinations)
- Per Astro docs: These property combinations should not be used together. Testing graceful handling.
-
-
-
-
31. CONFLICT: densities + layout (densities should be ignored)
-
-
-
-
-
-
32. CONFLICT: densities + widths (only one should be used)
-
-
-
-
-
-
33. CONFLICT: widths without sizes (sizes required)
-
-
-
-
-
-
34. CONFLICT: layout=fixed + widths (fixed layout shouldn't vary widths)
-
-
-
-
-
-
35. CONFLICT: layout=none + responsive=true (none disables responsiveness)
-
-
-
-
- Valid Property Combinations
- These property combinations work correctly together.
-
-
-
-
36. VALID: widths + sizes (responsive with explicit widths)
-
-
-
-
-
-
37. VALID: densities alone (density-based srcset)
-
-
-
-
-
-
38. VALID: layout=constrained + format + quality
-
-
-
-
-
-
39. VALID: layout=full-width with responsive props
-
-
-
-
-
-
40. VALID: responsive=true + ImageKit breakpoints
-
-
-
-
- Edge Case Tests
- Testing extreme values and boundary conditions.
-
-
-
-
41. Minimal dimensions (1px × 1px)
-
-
-
-
-
-
42. Extreme aspect ratio (1:1000)
-
-
-
-
-
-
43. Extreme aspect ratio (1000:1)
-
-
-
-
44. Quality = 0 (minimum)
+
Quality = 0 (minimum)
-
45. Quality = 100 (maximum)
+
Quality = 100 (maximum)
-
46. Format = webp
+
Format = webp
-
47. Format = avif
+
Format = avif
-
48. Format = svg
+
Format = svg
-
49. Multiple complex transformations
+
Multiple complex transformations
-
50. transformationPosition="path" + transformations
+
transformationPosition="path" + transformations
-
51. transformationPosition="query" + queryParameters
+ transformationPosition="query" + queryParameters
-
-
-
52. Large width (5000px)
-
-
-
-
53. Quality preset = "low"
+
Quality preset = "low"
-
54. Quality preset = "mid"
+
Quality preset = "mid"
-
55. Quality preset = "max"
+ Quality preset = "max"
-
-
-
56. Responsive with extreme device breakpoints
+
+
Sharp delegation (non-ImageKit srcs)
+
The following cases use srcs that are not ImageKit URLs. The integration delegates them to Astro's default sharp service, so they're served via Astro's /_image endpoint instead of being rewritten to ik.imagekit.io.
+
+
+
+
Local imported asset (sharp)
-
-
-
57. Responsive with extreme image breakpoints
+
+
+
Allow-listed external URL (sharp)
+
diff --git a/test-app/src/pages/index.astro b/test-app/src/pages/index.astro
index c2a3b8f..ca8ff4e 100644
--- a/test-app/src/pages/index.astro
+++ b/test-app/src/pages/index.astro
@@ -48,6 +48,8 @@
diff --git a/test-app/src/pages/md.md b/test-app/src/pages/md.md
new file mode 100644
index 0000000..47ad05b
--- /dev/null
+++ b/test-app/src/pages/md.md
@@ -0,0 +1,32 @@
+---
+layout: null
+title: Markdown image test
+---
+
+# Markdown image test
+
+This page verifies that `` in `.md` files is processed by Astro's image pipeline and routed through the `@imagekit/astro` image service.
+
+
+
+## ImageKit URL (primary endpoint host)
+
+
+
+## Custom IK domain (additionalEndpoints host)
+
+
+
+## Local imported asset (delegated to sharp)
+
+
+
+## Allow-listed external URL (delegated to sharp)
+
+
+
+## Non-allow-listed external URL (rendered as-is by Astro)
+
+
+
+
diff --git a/test-app/src/pages/og.astro b/test-app/src/pages/og.astro
new file mode 100644
index 0000000..caa5717
--- /dev/null
+++ b/test-app/src/pages/og.astro
@@ -0,0 +1,60 @@
+---
+import { OgImage, getOgImageUrl } from '@imagekit/astro';
+
+// Compute one URL via the helper to demonstrate the escape hatch
+const customUrl = getOgImageUrl({
+ src: '/default-image.jpg',
+ width: 1200,
+ height: 630,
+ transformation: [{ blur: 5 }],
+});
+---
+
+
+
+
+
+
+
OG Image Test
+
+
+
+
+
+
+
+
+
<OgImage> Output
+
View source to see the generated <meta> tags injected into the page <head>.
+
+
Helper-built secondary URL
+
{customUrl}
+
+
+
+
diff --git a/test-app/src/pages/upload.astro b/test-app/src/pages/upload.astro
index f15e265..5a66af6 100644
--- a/test-app/src/pages/upload.astro
+++ b/test-app/src/pages/upload.astro
@@ -109,7 +109,7 @@