diff --git a/.changeset/sharp-suns-sing.md b/.changeset/sharp-suns-sing.md new file mode 100644 index 0000000000..0a455ccd11 --- /dev/null +++ b/.changeset/sharp-suns-sing.md @@ -0,0 +1,5 @@ +--- +'@lit-labs/ssr': patch +--- + +Fix incorrect attribute names being matched to values when attribute expressions are followed by element expressions such as using the `ref` directive. diff --git a/packages/labs/ssr/src/lib/render-value.ts b/packages/labs/ssr/src/lib/render-value.ts index 888d9c2020..b681ec882b 100644 --- a/packages/labs/ssr/src/lib/render-value.ts +++ b/packages/labs/ssr/src/lib/render-value.ts @@ -293,7 +293,6 @@ const getTemplateOpcodes = ( if (template !== undefined) { return template; } - // The property '_$litType$' needs to remain unminified. const [html, attrNames] = getTemplateHtml( result.strings, // SVG TemplateResultType functionality is only required on the client, @@ -445,16 +444,16 @@ const getTemplateOpcodes = ( // nodes with bindings, we don't account for it in the nodeIndex because // that will not be injected into the client template const strings = attr.value.split(marker); - // We store the case-sensitive name from `attrNames` (generated - // while parsing the template strings); note that this assumes - // parse5 attribute ordering matches string ordering - const name = attrNames[attrIndex++]; const attrSourceLocation = node.sourceCodeLocation!.attrs![attr.name]!; const attrNameStartOffset = attrSourceLocation.startOffset; const attrEndOffset = attrSourceLocation.endOffset; flushTo(attrNameStartOffset); if (isAttrBinding) { + // We store the case-sensitive name from `attrNames` (generated + // while parsing the template strings); note that this assumes + // parse5 attribute ordering matches string ordering + const name = attrNames[attrIndex++]; const [, prefix, caseSensitiveName] = /([.?@])?(.*)/.exec( name as string )!; diff --git a/packages/labs/ssr/src/test/integration/tests/basic.ts b/packages/labs/ssr/src/test/integration/tests/basic.ts index e24087ed5d..316989ea3e 100644 --- a/packages/labs/ssr/src/test/integration/tests/basic.ts +++ b/packages/labs/ssr/src/test/integration/tests/basic.ts @@ -3926,6 +3926,20 @@ export const tests: {[name: string]: SSRTest} = { stableSelectors: ['div', 'span', 'p'], }, + 'ElementPart followed by Multiple AttributeParts': { + render(x, y) { + const ref1 = createRef(); + return html`
`; + }, + expectations: [ + { + args: ['x', 'y'], + html: '', + }, + ], + stableSelectors: ['div'], + }, + 'All part types with at various depths': () => { const handler1 = (e: Event) => ((e.target as any).triggered1 = true); const handler2 = (e: Event) => ((e.target as any).triggered2 = true); diff --git a/packages/labs/ssr/src/test/lib/render-lit_test.ts b/packages/labs/ssr/src/test/lib/render-lit_test.ts index 7c1d0601c2..f754bdfdc5 100644 --- a/packages/labs/ssr/src/test/lib/render-lit_test.ts +++ b/packages/labs/ssr/src/test/lib/render-lit_test.ts @@ -207,6 +207,19 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { ); }); + test('multiple attribute expressions with string value preceded by element expression', async () => { + const {render, templateWithElementAndMultipleAttributeExpressions} = + await setup(); + const result = await render( + templateWithElementAndMultipleAttributeExpressions('foo', 'bar') + ); + // Has marker attribute for number of bound attributes. + assert.is( + result, + `` + ); + }); + test('attribute expression with multiple bindings', async () => { const {render, templateWithMultiBindingAttributeExpression} = await setup(); const result = await render( diff --git a/packages/labs/ssr/src/test/test-files/render-test-module.ts b/packages/labs/ssr/src/test/test-files/render-test-module.ts index 2495359f03..79f62d0541 100644 --- a/packages/labs/ssr/src/test/test-files/render-test-module.ts +++ b/packages/labs/ssr/src/test/test-files/render-test-module.ts @@ -7,6 +7,7 @@ import {html, svg, nothing} from 'lit'; import {repeat} from 'lit/directives/repeat.js'; import {classMap} from 'lit/directives/class-map.js'; +import {ref, createRef} from 'lit/directives/ref.js'; import {LitElement, css, PropertyValues} from 'lit'; import {property, customElement} from 'lit/decorators.js'; import {html as serverhtml} from '../../lib/server-template.js'; @@ -36,6 +37,11 @@ export const templateWithMultipleAttributeExpressions = ( y: string ) => html``; // prettier-ignore +export const templateWithElementAndMultipleAttributeExpressions = ( + x: string, + y: string +) => html``; +// prettier-ignore export const templateWithMultiBindingAttributeExpression = ( x: string, y: string