diff --git a/src/__tests__/property-controls/bindingCompatibility.test.ts b/src/__tests__/property-controls/bindingCompatibility.test.ts index 080d4372d..9273e552c 100644 --- a/src/__tests__/property-controls/bindingCompatibility.test.ts +++ b/src/__tests__/property-controls/bindingCompatibility.test.ts @@ -106,10 +106,18 @@ describe('isFieldBindable', () => { expect(isFieldBindable('url', metaField('f', 'email'))).toBe(true) }) + it('url + text → true (a slug composes into an href token)', () => { + expect(isFieldBindable('url', metaField('f', 'text'))).toBe(true) + }) + it('url + boolean → false', () => { expect(isFieldBindable('url', metaField('f', 'boolean'))).toBe(false) }) + it('url + richText → false (markup is not a URL fragment)', () => { + expect(isFieldBindable('url', metaField('f', 'richText'))).toBe(false) + }) + it('group + text → false (group has no bindings)', () => { expect(isFieldBindable('group', metaField('f', 'text'))).toBe(false) }) diff --git a/src/__tests__/utils/urlValidation.test.ts b/src/__tests__/utils/urlValidation.test.ts index 4e93c491c..dcd5b095c 100644 --- a/src/__tests__/utils/urlValidation.test.ts +++ b/src/__tests__/utils/urlValidation.test.ts @@ -60,17 +60,48 @@ describe('isValidUrl', () => { }) }) - describe('malformed input', () => { - it('rejects plain text without protocol', () => { - expect(isValidUrl('not a url')).toBe(false) + describe('relative references', () => { + it('accepts a root-relative path', () => { + expect(isValidUrl('/relative/path')).toBe(true) + }) + + it('accepts document-relative paths', () => { + expect(isValidUrl('./sibling.html')).toBe(true) + expect(isValidUrl('../parent/page')).toBe(true) + expect(isValidUrl('page.html')).toBe(true) + }) + + it('accepts in-page anchors and query-only references', () => { + expect(isValidUrl('#section')).toBe(true) + expect(isValidUrl('?q=1')).toBe(true) + }) + + it('accepts protocol-relative URLs', () => { + expect(isValidUrl('//example.com')).toBe(true) + }) + }) + + describe('data tokens', () => { + it('accepts a bare token', () => { + expect(isValidUrl('{currentEntry.permalink}')).toBe(true) }) - it('rejects relative paths', () => { - expect(isValidUrl('/relative/path')).toBe(false) + it('accepts a token composed into a relative path', () => { + expect(isValidUrl('/blog/{currentEntry.slug}')).toBe(true) }) - it('rejects protocol-relative URLs', () => { - expect(isValidUrl('//example.com')).toBe(false) + it('accepts a token composed into an absolute URL', () => { + expect(isValidUrl('https://example.com/{currentEntry.slug}')).toBe(true) + }) + }) + + describe('malformed input', () => { + it('rejects plain text containing spaces', () => { + expect(isValidUrl('not a url')).toBe(false) + }) + + it('rejects a relative path containing whitespace', () => { + expect(isValidUrl('/relative/pa th')).toBe(false) }) }) }) @@ -176,5 +207,11 @@ describe('isValidImageUrl', () => { expect(isValidUrl(url)).toBe(false) expect(isValidImageUrl(url)).toBe(true) }) + + it('relative references are valid for isValidUrl but not isValidImageUrl', () => { + const url = '/images/photo.png' + expect(isValidUrl(url)).toBe(true) + expect(isValidImageUrl(url)).toBe(false) + }) }) }) diff --git a/src/admin/pages/site/property-controls/DynamicBindingControl/helpers.ts b/src/admin/pages/site/property-controls/DynamicBindingControl/helpers.ts index 96db3cfcb..0c266282c 100644 --- a/src/admin/pages/site/property-controls/DynamicBindingControl/helpers.ts +++ b/src/admin/pages/site/property-controls/DynamicBindingControl/helpers.ts @@ -44,7 +44,10 @@ export function loopFieldMatchesControl( case 'textarea': return field.format !== 'media' && field.format !== 'html' case 'url': - return field.format === 'url' || field.format === 'media' + // Same reasoning as BINDING_COMPATIBILITY.url: token insertion builds the + // href as a string, so plain-text synthetics (`slug`, `publishedAt`, …) + // are valid path segments. Only html is excluded. + return field.format !== 'html' case 'number': case 'toggle': case 'color': diff --git a/src/admin/pages/site/property-controls/bindingCompatibility.ts b/src/admin/pages/site/property-controls/bindingCompatibility.ts index d1629cda6..9590d577c 100644 --- a/src/admin/pages/site/property-controls/bindingCompatibility.ts +++ b/src/admin/pages/site/property-controls/bindingCompatibility.ts @@ -48,7 +48,12 @@ export const BINDING_COMPATIBILITY: Record