Skip to content

Commit 1d8f062

Browse files
committed
Handle URL fragments in getAssetByPath
Fix #522
1 parent 1f5613a commit 1d8f062

2 files changed

Lines changed: 127 additions & 1 deletion

File tree

src/lib/services/assets/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,8 @@ export const getAssetByAbsolutePath = ({ path, entry, collectionName, fileName }
273273
* @returns {Asset | undefined} Corresponding asset.
274274
*/
275275
export const getAssetByPath = ({ value, entry, collectionName, fileName }) => {
276-
const path = decodeFilePath(value);
276+
// Remove potential fragment before decoding
277+
const path = decodeFilePath(value.split('#')[0]);
277278

278279
// Handle a relative path. A path starting with `@`, like `@assets/images/...` is a special case,
279280
// considered as an absolute path.

src/lib/services/assets/index.test.js

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,11 @@ describe('assets/index', () => {
239239
});
240240

241241
describe('getAssetByPath', () => {
242+
/**
243+
* @type {import('vitest').MockedFunction<typeof
244+
* import('$lib/services/utils/file').decodeFilePath
245+
* >}
246+
*/
242247
let decodeFilePathMock;
243248

244249
beforeEach(async () => {
@@ -343,6 +348,126 @@ describe('assets/index', () => {
343348
expect(result).toBeUndefined();
344349
});
345350

351+
it('should remove URL fragments before processing path', async () => {
352+
const { stripSlashes } = await import('@sveltia/utils/string');
353+
const stripSlashesMock = vi.mocked(stripSlashes);
354+
355+
stripSlashesMock.mockReturnValue('/assets/image.jpg');
356+
357+
const mockAsset = {
358+
path: '/assets/image.jpg',
359+
name: 'image.jpg',
360+
kind: /** @type {import('$lib/types/private').AssetKind} */ ('image'),
361+
sha: 'abc123',
362+
size: 1024,
363+
folder: {
364+
internalPath: 'assets',
365+
publicPath: '/assets',
366+
collectionName: undefined,
367+
entryRelative: false,
368+
hasTemplateTags: false,
369+
},
370+
};
371+
372+
allAssets.set([mockAsset]);
373+
374+
const result = getAssetByPath({
375+
value: '/assets/image.jpg#fragment',
376+
collectionName: 'posts',
377+
});
378+
379+
expect(result).toEqual(mockAsset);
380+
expect(decodeFilePathMock).toHaveBeenCalledWith('/assets/image.jpg');
381+
expect(stripSlashesMock).toHaveBeenCalledWith('/assets/image.jpg');
382+
});
383+
384+
it('should remove URL fragments with complex hashes', async () => {
385+
const { stripSlashes } = await import('@sveltia/utils/string');
386+
const stripSlashesMock = vi.mocked(stripSlashes);
387+
388+
stripSlashesMock.mockReturnValue('/assets/image.jpg');
389+
390+
const mockAsset = {
391+
path: '/assets/image.jpg',
392+
name: 'image.jpg',
393+
kind: /** @type {import('$lib/types/private').AssetKind} */ ('image'),
394+
sha: 'abc123',
395+
size: 1024,
396+
folder: {
397+
internalPath: 'assets',
398+
publicPath: '/assets',
399+
collectionName: undefined,
400+
entryRelative: false,
401+
hasTemplateTags: false,
402+
},
403+
};
404+
405+
allAssets.set([mockAsset]);
406+
407+
const result = getAssetByPath({
408+
value: '/assets/image.jpg#section:subsection',
409+
collectionName: 'posts',
410+
});
411+
412+
expect(result).toEqual(mockAsset);
413+
expect(decodeFilePathMock).toHaveBeenCalledWith('/assets/image.jpg');
414+
});
415+
416+
it('should handle relative paths with fragments', async () => {
417+
const { resolvePath } = await import('$lib/services/utils/file');
418+
const { getAssociatedCollections } = await import('$lib/services/contents/entry');
419+
const { getCollectionFilesByEntry } = await import('$lib/services/contents/collection/files');
420+
const resolvePathMock = vi.mocked(resolvePath);
421+
const getAssociatedCollectionsMock = vi.mocked(getAssociatedCollections);
422+
const getCollectionFilesByEntryMock = vi.mocked(getCollectionFilesByEntry);
423+
424+
const mockEntry = /** @type {any} */ ({
425+
slug: 'test-post',
426+
id: 'test-post',
427+
subPath: 'test-post.md',
428+
locales: {
429+
en: {
430+
path: 'content/posts/test-post.md',
431+
content: { title: 'Test Post' },
432+
},
433+
},
434+
});
435+
436+
const mockCollection = /** @type {any} */ ({
437+
name: 'posts',
438+
_i18n: { defaultLocale: 'en' },
439+
});
440+
441+
const mockAsset = {
442+
path: 'content/posts/image.jpg',
443+
name: 'image.jpg',
444+
kind: /** @type {import('$lib/types/private').AssetKind} */ ('image'),
445+
sha: 'abc123',
446+
size: 1024,
447+
folder: {
448+
internalPath: 'content/posts',
449+
publicPath: '/posts',
450+
collectionName: 'posts',
451+
entryRelative: false,
452+
hasTemplateTags: false,
453+
},
454+
};
455+
456+
allAssets.set([mockAsset]);
457+
resolvePathMock.mockReturnValue('content/posts/image.jpg');
458+
getAssociatedCollectionsMock.mockReturnValue([mockCollection]);
459+
getCollectionFilesByEntryMock.mockReturnValue([]);
460+
461+
const result = getAssetByPath({
462+
value: 'image.jpg#anchor',
463+
entry: mockEntry,
464+
collectionName: 'posts',
465+
});
466+
467+
expect(result).toEqual(mockAsset);
468+
expect(decodeFilePathMock).toHaveBeenCalledWith('image.jpg');
469+
});
470+
346471
it('should handle @ prefixed paths as absolute', async () => {
347472
const { stripSlashes } = await import('@sveltia/utils/string');
348473
const stripSlashesMock = vi.mocked(stripSlashes);

0 commit comments

Comments
 (0)