Fix data binding replacing partial matches

This commit is contained in:
Andrew Kingston 2025-04-15 16:35:33 +01:00
parent 67786ea58a
commit c9cf9ad506
No known key found for this signature in database
2 changed files with 39 additions and 0 deletions

View File

@ -1317,6 +1317,22 @@ const shouldReplaceBinding = (currentValue, from, convertTo, binding) => {
return !invalids.find(invalid => noSpaces?.includes(invalid))
}
// If converting readable to runtime we need to ensure we don't replace words
// which are substrings of other words - e.g. a binding of `a` would turn
// `hah` into `h[a]h` which is obviously wrong. To avoid this we can strip all
// expanded versions of the binding, then ensure the binding still exists.
const excludeExtensions = (string, binding) => {
// Regex to find prefixed bindings (e.g. exclude xfoo for foo)
const regex1 = new RegExp(`[a-zA-Z0-9-_]+${binding}[a-zA-Z0-9-_]*`, "g")
// Regex to find prefixed bindings (e.g. exclude foox for foo)
const regex2 = new RegExp(`[a-zA-Z0-9-_]*${binding}[a-zA-Z0-9-_]+`, "g")
const matches = [...string.matchAll(regex1), ...string.matchAll(regex2)]
for (let match of matches) {
string = string.replace(match[0], new Array(match[0].length + 1).join("*"))
}
return string
}
/**
* Utility function which replaces a string between given indices.
*/
@ -1361,6 +1377,10 @@ const bindingReplacement = (
// in the search, working from longest to shortest so always use best match first
let searchString = newBoundValue
for (let from of convertFromProps) {
// Blank out all extensions of this string to avoid partial matches
if (convertTo === "runtimeBinding") {
searchString = excludeExtensions(searchString, from)
}
const binding = bindableProperties.find(el => el[convertFrom] === from)
if (
isJS ||

View File

@ -72,6 +72,13 @@ describe("Builder dataBinding", () => {
runtimeBinding: "count",
type: "context",
},
{
category: "Bindings",
icon: "Brackets",
readableBinding: "location",
runtimeBinding: "[location]",
type: "context",
},
]
it("should convert a readable binding to a runtime one", () => {
const textWithBindings = `Hello {{ Current User.firstName }}! The count is {{ Binding.count }}.`
@ -83,6 +90,18 @@ describe("Builder dataBinding", () => {
)
).toEqual(`Hello {{ [user].[firstName] }}! The count is {{ count }}.`)
})
it("should not convert a partial match", () => {
const textWithBindings = `location {{ _location Zlocation location locationZ _location_ }}`
expect(
readableToRuntimeBinding(
bindableProperties,
textWithBindings,
"runtimeBinding"
)
).toEqual(
`location {{ _location Zlocation [location] locationZ _location_ }}`
)
})
})
describe("updateReferencesInObject", () => {