[FIX] blockdom: properly merge dynamic class values

Class attributes are managed in a specific way in owl: they are merged
with existing class attributes, and also, they can be defined in
multiple ways (t-att-class, t-attf-class, t-att=['class', ...] and each
of these can be combined together.

Before this commit, the `t-att` syntax was handled as a normal
attribute, and therefore, would not combine as required with existing
classes.

closes #1453
This commit is contained in:
Géry Debongnie
2023-06-19 14:36:59 +02:00
committed by Sam Degueldre
parent 59c49b5833
commit 23c7d19ef0
4 changed files with 202 additions and 5 deletions
+25 -5
View File
@@ -36,10 +36,18 @@ export function createAttrUpdater(attr: string): Setter<HTMLElement> {
export function attrsSetter(this: HTMLElement, attrs: any) {
if (isArray(attrs)) {
setAttribute.call(this, attrs[0], attrs[1]);
if (attrs[0] === "class") {
setClass.call(this, attrs[1]);
} else {
setAttribute.call(this, attrs[0], attrs[1]);
}
} else {
for (let k in attrs) {
setAttribute.call(this, k, attrs[k]);
if (k === "class") {
setClass.call(this, attrs[k]);
} else {
setAttribute.call(this, k, attrs[k]);
}
}
}
}
@@ -52,7 +60,11 @@ export function attrsUpdater(this: HTMLElement, attrs: any, oldAttrs: any) {
if (val === oldAttrs[1]) {
return;
}
setAttribute.call(this, name, val);
if (name === "class") {
updateClass.call(this, val, oldAttrs[1]);
} else {
setAttribute.call(this, name, val);
}
} else {
removeAttribute.call(this, oldAttrs[0]);
setAttribute.call(this, name, val);
@@ -60,13 +72,21 @@ export function attrsUpdater(this: HTMLElement, attrs: any, oldAttrs: any) {
} else {
for (let k in oldAttrs) {
if (!(k in attrs)) {
removeAttribute.call(this, k);
if (k === "class") {
updateClass.call(this, "", oldAttrs[k]);
} else {
removeAttribute.call(this, k);
}
}
}
for (let k in attrs) {
const val = attrs[k];
if (val !== oldAttrs[k]) {
setAttribute.call(this, k, val);
if (k === "class") {
updateClass.call(this, val, oldAttrs[k]);
} else {
setAttribute.call(this, k, val);
}
}
}
}
+31
View File
@@ -145,3 +145,34 @@ test("class attribute (with a preexisting value", async () => {
patch(tree, block([""]));
expect(fixture.innerHTML).toBe(`<div class="tomato"></div>`);
});
test("block-class attributes with preexisting class attribute", async () => {
const block = createBlock('<div block-attributes="0" class="owl"></div>');
const tree = block([{ class: "eagle" }]);
mount(tree, fixture);
expect(fixture.innerHTML).toBe(`<div class="owl eagle"></div>`);
patch(tree, block([{ class: "falcon" }]));
expect(fixture.innerHTML).toBe(`<div class="owl falcon"></div>`);
patch(tree, block([{}]));
expect(fixture.innerHTML).toBe(`<div class="owl"></div>`);
});
test("block-class attributes (array syntax) with preexisting class attribute", async () => {
const block = createBlock('<div block-attributes="0" class="owl"></div>');
const tree = block([["class", "eagle"]]);
mount(tree, fixture);
expect(fixture.innerHTML).toBe(`<div class="owl eagle"></div>`);
patch(tree, block([["class", "falcon"]]));
expect(fixture.innerHTML).toBe(`<div class="owl falcon"></div>`);
patch(tree, block([["class", ""]]));
expect(fixture.innerHTML).toBe(`<div class="owl"></div>`);
patch(tree, block([["class", "buzzard"]]));
expect(fixture.innerHTML).toBe(`<div class="owl buzzard"></div>`);
});
@@ -707,6 +707,123 @@ exports[`attributes updating classes (with obj notation) 1`] = `
}"
`;
exports[`attributes various combinations of class, t-att-class, and t-att 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div block-attributes=\\"0\\" class=\\"c\\">content</div>\`);
return function template(ctx, node, key = \\"\\") {
let attr1 = {class:'a'};
return block1([attr1]);
}
}"
`;
exports[`attributes various combinations of class, t-att-class, and t-att 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div block-attributes=\\"0\\" block-attribute-1=\\"class\\" class=\\"c\\">content</div>\`);
return function template(ctx, node, key = \\"\\") {
let attr1 = {class:'a'};
let attr2 = {'b':true};
return block1([attr1, attr2]);
}
}"
`;
exports[`attributes various combinations of class, t-att-class, and t-att 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div block-attributes=\\"0\\" class=\\"c\\" block-attribute-1=\\"class\\">content</div>\`);
return function template(ctx, node, key = \\"\\") {
let attr1 = {class:'a'};
let attr2 = {'b':true};
return block1([attr1, attr2]);
}
}"
`;
exports[`attributes various combinations of class, t-att-class, and t-att 4`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div class=\\"c\\" block-attributes=\\"0\\" block-attribute-1=\\"class\\">content</div>\`);
return function template(ctx, node, key = \\"\\") {
let attr1 = {class:'a'};
let attr2 = {'b':true};
return block1([attr1, attr2]);
}
}"
`;
exports[`attributes various combinations of class, t-att-class, and t-att 5`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div class=\\"c\\" block-attribute-0=\\"class\\" block-attributes=\\"1\\">content</div>\`);
return function template(ctx, node, key = \\"\\") {
let attr1 = {'b':true};
let attr2 = {class:'a'};
return block1([attr1, attr2]);
}
}"
`;
exports[`attributes various combinations of class, t-att-class, and t-att 6`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div class=\\"c\\" block-attribute-0=\\"class\\">content</div>\`);
return function template(ctx, node, key = \\"\\") {
let attr1 = {'b':true};
return block1([attr1]);
}
}"
`;
exports[`attributes various combinations of class, t-att-class, and t-att 7`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div class=\\"c\\" block-attribute-0=\\"class\\">content</div>\`);
return function template(ctx, node, key = \\"\\") {
let attr1 = ('b');
return block1([attr1]);
}
}"
`;
exports[`attributes various combinations of class, t-att-class, and t-att 8`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div block-attributes=\\"0\\" block-attribute-1=\\"class\\">content</div>\`);
return function template(ctx, node, key = \\"\\") {
let attr1 = {class:'a'};
let attr2 = {'b':true};
return block1([attr1, attr2]);
}
}"
`;
exports[`attributes various escapes 1`] = `
"function anonymous(app, bdom, helpers
) {
+29
View File
@@ -371,4 +371,33 @@ describe("attributes", () => {
// not sure about this. maybe we want to remove the attribute?
expect(fixture.innerHTML).toBe('<div class="hoy a b"></div>');
});
test("various combinations of class, t-att-class, and t-att", () => {
const template1 = `<div t-att="{ class: 'a' }" class="c">content</div>`;
expect(renderToString(template1)).toBe('<div class="c a">content</div>');
const template2 = `<div t-att="{ class: 'a' }" t-att-class="{'b': true}" class="c">content</div>`;
expect(renderToString(template2)).toBe('<div class="c a b">content</div>');
const template3 = `<div t-att="{ class: 'a' }" class="c" t-att-class="{'b': true}">content</div>`;
expect(renderToString(template3)).toBe('<div class="c a b">content</div>');
const template4 = `<div class="c" t-att="{ class: 'a' }" t-att-class="{'b': true}">content</div>`;
expect(renderToString(template4)).toBe('<div class="c a b">content</div>');
const template5 = `<div class="c" t-att-class="{'b': true}" t-att="{ class: 'a' }">content</div>`;
expect(renderToString(template5)).toBe('<div class="c b a">content</div>');
const template6 = `<div class="c" t-att-class="{'b': true}">content</div>`;
expect(renderToString(template6)).toBe('<div class="c b">content</div>');
const template7 = `<div class="c" t-attf-class="{{'b'}}">content</div>`;
expect(renderToString(template7)).toBe('<div class="c b">content</div>');
const template8 = `<div t-att="{ class: 'a' }" class="c" t-att-class="{'b': true}">content</div>`;
expect(renderToString(template8)).toBe('<div class="c a b">content</div>');
const template9 = `<div t-att="{ class: 'a' }" t-att-class="{'b': true}">content</div>`;
expect(renderToString(template9)).toBe('<div class="a b">content</div>');
});
});