packages/__tests__/src/3-runtime-html/has-multi-bindings.unit.spec.ts
import { Char } from '../util.js';
import { assert } from '@aurelia/testing';
describe('3-runtime-html/has-multi-bindings.unit.spec.ts', function () {
interface IBindingSpec {
target: string;
rawValue: string;
}
interface ICustomAttributeSymbolSpec {
attrValue: string;
expectedBindings: IBindingSpec[];
}
const specs: ICustomAttributeSymbolSpec[] = [
{
attrValue: 'color',
expectedBindings: [
{
target: 'attr',
rawValue: 'color',
},
],
},
{
attrValue: `\${c}`,
expectedBindings: [
{
target: 'attr',
rawValue: `\${c}`,
},
],
},
{
attrValue: 'http\\://abc.def',
expectedBindings: [
{
target: 'attr',
rawValue: 'http\\://abc.def',
},
],
},
{
attrValue: '1111',
expectedBindings: [
{
target: 'attr',
rawValue: '1111',
},
],
},
{
attrValue: '1.11',
expectedBindings: [
{
target: 'attr',
rawValue: '1.11',
},
],
},
{
attrValue: '\\:\\:Math',
expectedBindings: [
{
target: 'attr',
rawValue: '\\:\\:Math',
},
],
},
{
attrValue: '\\:\\:\\:\\:\\:Math',
expectedBindings: [
{
target: 'attr',
rawValue: '\\:\\:\\:\\:\\:Math',
},
],
},
{
attrValue: `\${a | b:{c:b}}`,
expectedBindings: [
{
target: 'attr',
rawValue: `\${a | b:{c:b}}`,
},
],
},
{
attrValue: `\${a & b:{c:b}}`,
expectedBindings: [
{
target: 'attr',
rawValue: `\${a & b:{c:b}}`,
},
],
},
{
attrValue: `\${a & b:{c:b}} \${a & b:{c:b}}`,
expectedBindings: [
{
target: 'attr',
rawValue: `\${a & b:{c:b}} \${a & b:{c:b}}`,
},
],
},
{
attrValue: 'a:b',
expectedBindings: [
{
target: 'a',
rawValue: 'b',
},
],
},
{
attrValue: 'a:a;b: b',
expectedBindings: [
{
target: 'a',
rawValue: 'a',
},
{
target: 'b',
rawValue: 'b',
},
],
},
{
attrValue: 'a:1;b: 2',
expectedBindings: [
{
target: 'a',
rawValue: '1',
},
{
target: 'b',
rawValue: '2',
},
],
},
{
attrValue: 'a.bind:1;b: 2',
expectedBindings: [
{
target: 'a.bind',
rawValue: '1',
},
{
target: 'b',
rawValue: '2',
},
],
},
{
attrValue: 'a:1; b.bind: 2',
expectedBindings: [
{
target: 'a',
rawValue: '1',
},
{
target: 'b.bind',
rawValue: '2',
},
],
},
{
attrValue: 'a:1 | c:d; b.bind: 2',
expectedBindings: [
{
target: 'a',
rawValue: '1 | c:d',
},
{
target: 'b.bind',
rawValue: '2',
},
],
},
{
attrValue: 'a.bind:1 | c:d; b.bind: 2',
expectedBindings: [
{
target: 'a.bind',
rawValue: '1 | c:d',
},
{
target: 'b.bind',
rawValue: '2',
},
],
},
{
attrValue: `a: \${a | c:d} abcd; b.bind: 2`,
expectedBindings: [
{
target: 'a',
rawValue: `\${a | c:d} abcd`,
},
{
target: 'b.bind',
rawValue: '2',
},
],
},
{
attrValue: 'a: http\\:/ahbc.def; b.bind: 2',
expectedBindings: [
{
target: 'a',
rawValue: 'http\\:/ahbc.def',
},
{
target: 'b.bind',
rawValue: '2',
},
],
},
{
attrValue: 'a: mainRoute; b.bind: { name: name, address, id: userId }',
expectedBindings: [
{
target: 'a',
rawValue: 'mainRoute',
},
{
target: 'b.bind',
rawValue: '{ name: name, address, id: userId }',
},
],
},
{
attrValue: 'b.bind: { name: name, address, id: userId }; a: mainRoute;',
expectedBindings: [
{
target: 'a',
rawValue: 'mainRoute',
},
{
target: 'b.bind',
rawValue: '{ name: name, address, id: userId }',
},
],
},
{
attrValue: 'b.bind: { name: name, address, id: userId } | normalizeAddress; a: mainRoute',
expectedBindings: [
{
target: 'a',
rawValue: 'mainRoute',
},
{
target: 'b.bind',
rawValue: '{ name: name, address, id: userId } | normalizeAddress',
},
],
},
{
attrValue: 'b.bind: { name: name, address, id: userId } | normalizeAddress; a: mainRoute;',
expectedBindings: [
{
target: 'a',
rawValue: 'mainRoute',
},
{
target: 'b.bind',
rawValue: '{ name: name, address, id: userId } | normalizeAddress',
},
],
},
{
attrValue: 'b.bind: { name: name, address, id: userId } | normalizeAddress:`en-us`; a: mainRoute',
expectedBindings: [
{
target: 'a',
rawValue: 'mainRoute',
},
{
target: 'b.bind',
rawValue: '{ name: name, address, id: userId } | normalizeAddress:`en-us`',
},
],
},
{
attrValue: 'b.bind: { name: name, address, id: userId } | normalizeAddress:`en-us`; a: mainRoute;',
expectedBindings: [
{
target: 'a',
rawValue: 'mainRoute',
},
{
target: 'b.bind',
rawValue: '{ name: name, address, id: userId } | normalizeAddress:`en-us`',
},
],
},
];
for (const { attrValue, expectedBindings } of specs) {
it(`[UNIT] attr="${attrValue}"`, function () {
if (hasInlineBindings(attrValue)) {
parse(attrValue).forEach((pair) => {
const expectedBinding = expectedBindings.find(eb => eb.target === pair.target);
assert.deepStrictEqual(pair, expectedBinding);
});
} else {
assert.deepStrictEqual({ target: 'attr', rawValue: attrValue }, expectedBindings[0]);
}
});
}
// this is an extraction of the core parsing logic inside the template compiler
// to be able to unit test an important logic
// thanks to @fkleuver
function hasInlineBindings(value: string): boolean {
const len = value.length;
let ch = 0;
for (let i = 0; i < len; ++i) {
ch = value.charCodeAt(i);
if (ch === Char.Backslash) {
++i;
// Ignore whatever comes next because it's escaped
} else if (ch === Char.Colon) {
return true;
} else if (ch === Char.Dollar && value.charCodeAt(i + 1) === Char.OpenBrace) {
return false;
}
}
return false;
}
function parse(value: string): { target: string; rawValue: string }[] {
const valueLength = value.length;
const pairs: { target: string; rawValue: string }[] = [];
let attrName: string | undefined = void 0;
let attrValue: string | undefined = void 0;
let start = 0;
let ch = 0;
for (let i = 0; i < valueLength; ++i) {
ch = value.charCodeAt(i);
if (ch === Char.Backslash) {
++i;
// Ignore whatever comes next because it's escaped
} else if (ch === Char.Colon) {
attrName = value.slice(start, i);
// Skip whitespace after colon
while (value.charCodeAt(++i) <= Char.Space);
start = i;
for (; i < valueLength; ++i) {
ch = value.charCodeAt(i);
if (ch === Char.Backslash) {
++i;
// Ignore whatever comes next because it's escaped
} else if (ch === Char.Semicolon) {
attrValue = value.slice(start, i);
break;
}
}
if (attrValue === void 0) {
// No semicolon found, so just grab the rest of the value
attrValue = value.slice(start);
}
pairs.push({ target: attrName, rawValue: attrValue });
// Skip whitespace after semicolon
while (i < valueLength && value.charCodeAt(++i) <= Char.Space);
start = i;
attrName = void 0;
attrValue = void 0;
}
}
return pairs;
}
});