From 23c7d19ef0d930fac32dbc1fc1153c55253eac0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Mon, 19 Jun 2023 14:36:59 +0200 Subject: [PATCH] [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 --- src/runtime/blockdom/attributes.ts | 30 ++++- tests/blockdom/block_attributes.test.ts | 31 +++++ .../__snapshots__/attributes.test.ts.snap | 117 ++++++++++++++++++ tests/compiler/attributes.test.ts | 29 +++++ 4 files changed, 202 insertions(+), 5 deletions(-) diff --git a/src/runtime/blockdom/attributes.ts b/src/runtime/blockdom/attributes.ts index 422ed76a..ea7b4780 100644 --- a/src/runtime/blockdom/attributes.ts +++ b/src/runtime/blockdom/attributes.ts @@ -36,10 +36,18 @@ export function createAttrUpdater(attr: string): Setter { 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); + } } } } diff --git a/tests/blockdom/block_attributes.test.ts b/tests/blockdom/block_attributes.test.ts index 1085473b..6b6f3811 100644 --- a/tests/blockdom/block_attributes.test.ts +++ b/tests/blockdom/block_attributes.test.ts @@ -145,3 +145,34 @@ test("class attribute (with a preexisting value", async () => { patch(tree, block([""])); expect(fixture.innerHTML).toBe(`
`); }); + +test("block-class attributes with preexisting class attribute", async () => { + const block = createBlock('
'); + const tree = block([{ class: "eagle" }]); + + mount(tree, fixture); + expect(fixture.innerHTML).toBe(`
`); + + patch(tree, block([{ class: "falcon" }])); + expect(fixture.innerHTML).toBe(`
`); + + patch(tree, block([{}])); + expect(fixture.innerHTML).toBe(`
`); +}); + +test("block-class attributes (array syntax) with preexisting class attribute", async () => { + const block = createBlock('
'); + const tree = block([["class", "eagle"]]); + + mount(tree, fixture); + expect(fixture.innerHTML).toBe(`
`); + + patch(tree, block([["class", "falcon"]])); + expect(fixture.innerHTML).toBe(`
`); + + patch(tree, block([["class", ""]])); + expect(fixture.innerHTML).toBe(`
`); + + patch(tree, block([["class", "buzzard"]])); + expect(fixture.innerHTML).toBe(`
`); +}); diff --git a/tests/compiler/__snapshots__/attributes.test.ts.snap b/tests/compiler/__snapshots__/attributes.test.ts.snap index 9dd9e4b2..75f17605 100644 --- a/tests/compiler/__snapshots__/attributes.test.ts.snap +++ b/tests/compiler/__snapshots__/attributes.test.ts.snap @@ -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(\`
content
\`); + + 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(\`
content
\`); + + 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(\`
content
\`); + + 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(\`
content
\`); + + 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(\`
content
\`); + + 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(\`
content
\`); + + 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(\`
content
\`); + + 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(\`
content
\`); + + 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 ) { diff --git a/tests/compiler/attributes.test.ts b/tests/compiler/attributes.test.ts index 658783de..079a2b63 100644 --- a/tests/compiler/attributes.test.ts +++ b/tests/compiler/attributes.test.ts @@ -371,4 +371,33 @@ describe("attributes", () => { // not sure about this. maybe we want to remove the attribute? expect(fixture.innerHTML).toBe('
'); }); + + test("various combinations of class, t-att-class, and t-att", () => { + const template1 = `
content
`; + expect(renderToString(template1)).toBe('
content
'); + + const template2 = `
content
`; + expect(renderToString(template2)).toBe('
content
'); + + const template3 = `
content
`; + expect(renderToString(template3)).toBe('
content
'); + + const template4 = `
content
`; + expect(renderToString(template4)).toBe('
content
'); + + const template5 = `
content
`; + expect(renderToString(template5)).toBe('
content
'); + + const template6 = `
content
`; + expect(renderToString(template6)).toBe('
content
'); + + const template7 = `
content
`; + expect(renderToString(template7)).toBe('
content
'); + + const template8 = `
content
`; + expect(renderToString(template8)).toBe('
content
'); + + const template9 = `
content
`; + expect(renderToString(template9)).toBe('
content
'); + }); });