@microsoft/fast-html 1.0.0-alpha.6 → 1.0.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,7 +47,15 @@ Example:
47
47
 
48
48
  ### Syntax
49
49
 
50
- All bindings use a handlebars syntax.
50
+ All bindings use a handlebars-like syntax.
51
+
52
+ Some bindings are only relevant to the browser, such as for click handlers or other pieces of dynamic interaction. As such, their bindings use single curly braces `{}`, this is to prevent an intial SSR (Server Side Rendering) or other build time rendering technologies from needing to interpret them.
53
+
54
+ If a binding is relevant to both client and the back end rendering engine, it will use `{{}}` or `{{{}}}` depending on what type of data injection is being done.
55
+
56
+ Browser-only bindings:
57
+ - Event bindings
58
+ - Attribute directives
51
59
 
52
60
  #### Content binding
53
61
 
@@ -60,45 +68,47 @@ All bindings use a handlebars syntax.
60
68
  Event bindings must include the `()` as well as being preceeded by `@` in keeping with `@microsoft/fast-element` tagged template `html` syntax.
61
69
 
62
70
  ```html
63
- <button @click="{{handleClick()}}"></button>
71
+ <button @click="{handleClick()}"></button>
64
72
  ```
65
73
 
66
74
  In addition you may include an event or attribute or observable, events are denoted with `e` as a reserved letter.
67
75
 
68
76
  Event:
69
77
  ```html
70
- <button @click="{{handleClick(e)}}"></button>
78
+ <button @click="{handleClick(e)}"></button>
71
79
  ```
72
80
 
73
81
  Attribute/Observable:
74
82
  ```html
75
- <button @click="{{handleClick(foo)}}"></button>
83
+ <button @click="{handleClick(foo)}"></button>
76
84
  ```
77
85
 
78
86
  #### Directives
79
87
 
80
88
  Directives are assumed to be either an attribute directive or a directive that also serves a template. Both are prepended by `f-`. The logic of these directives and what their use cases are is explained in the [FAST html documentation](https://fast.design/docs/getting-started/html-directives).
81
89
 
90
+ Attribute directives are part of [client side binding](#syntax) and therefore use the `{}` syntax.
91
+
82
92
  Attribute directives include:
83
93
  - **slotted**
84
94
 
85
95
  Example:
86
96
  ```html
87
- <slot f-slotted="{{slottedNodes}}"></slot>
97
+ <slot f-slotted="{slottedNodes}"></slot>
88
98
  ```
89
99
 
90
100
  - **children**
91
101
 
92
102
  Example:
93
103
  ```html
94
- <ul f-children="{{listItems}}"><f-repeat value="{{item in list}}"><li>{{item}}</li></f-repeat></ul>
104
+ <ul f-children="{listItems}"><f-repeat value="{{item in list}}"><li>{{item}}</li></f-repeat></ul>
95
105
  ```
96
106
 
97
107
  - **ref**
98
108
 
99
109
  Example:
100
110
  ```html
101
- <video f-ref="{{video}}"></video>
111
+ <video f-ref="{video}"></video>
102
112
  ```
103
113
 
104
114
  Template directives include:
@@ -152,6 +162,15 @@ Where the right operand can be either a reference to a value (string e.g. `{{foo
152
162
  <f-apply partial="test" value="{{items}}"></f-apply>
153
163
  ```
154
164
 
165
+ #### Unescaped HTML
166
+
167
+ You can add unescaped HTML using triple braces, this will create an additional `div` element as the HTML needs an element to bind to. Where possible it is advisable to not use unescaped HTML and instead use other binding techniques as well as partials.
168
+
169
+ Example:
170
+ ```html
171
+ {{{html}}}
172
+ ```
173
+
155
174
  ### Writing Components
156
175
 
157
176
  When writing components with the intention of using the declarative HTML syntax, it is imperative that components are written with styling and rendering of the component to be less reliant on any JavaScript state management. An example of this is relying on `elementInterals` state to style a component.
@@ -1,6 +1,7 @@
1
1
  type BehaviorType = "dataBinding" | "templateDirective";
2
2
  type TemplateDirective = "when" | "repeat" | "apply";
3
3
  export type AttributeDirective = "children" | "slotted" | "ref";
4
+ type DataBindingBindingType = "client" | "default" | "unescaped";
4
5
  interface BehaviorConfig {
5
6
  type: BehaviorType;
6
7
  }
@@ -18,6 +19,7 @@ export interface AttributeDirectiveBindingBehaviorConfig extends BaseDataBinding
18
19
  export type DataBindingBehaviorConfig = ContentDataBindingBehaviorConfig | AttributeDataBindingBehaviorConfig | AttributeDirectiveBindingBehaviorConfig;
19
20
  export interface BaseDataBindingBehaviorConfig extends BehaviorConfig {
20
21
  type: "dataBinding";
22
+ bindingType: DataBindingBindingType;
21
23
  openingStartIndex: number;
22
24
  openingEndIndex: number;
23
25
  closingStartIndex: number;
@@ -99,4 +101,11 @@ interface OperatorConfig {
99
101
  * @returns Operator
100
102
  */
101
103
  export declare function getOperator(value: string): OperatorConfig;
104
+ /**
105
+ * This is the transform utility for rationalizing declarative HTML syntax
106
+ * with bindings in the ViewTemplate
107
+ * @param innerHTML The innerHTML to transform
108
+ * @param index The index to start the current slice of HTML to evaluate
109
+ */
110
+ export declare function transformInnerHTML(innerHTML: string, index?: number): string;
102
111
  export {};
@@ -1,7 +1,12 @@
1
1
  import { __awaiter, __decorate, __metadata } from "tslib";
2
- import { attr, FAST, FASTElement, fastElementRegistry, ViewTemplate, } from "@microsoft/fast-element";
2
+ import { attr, DOMAspect, FAST, FASTElement, fastElementRegistry, ViewTemplate, } from "@microsoft/fast-element";
3
3
  import { DOMPolicy } from "@microsoft/fast-element/dom-policy.js";
4
- import { getAllPartials, getNextBehavior, getOperator, pathResolver, } from "./utilities.js";
4
+ import { getAllPartials, getNextBehavior, getOperator, pathResolver, transformInnerHTML, } from "./utilities.js";
5
+ function allow(tagName, aspect, aspectName, sink) {
6
+ return (target, name, value, ...rest) => {
7
+ sink(target, name, value, ...rest);
8
+ };
9
+ }
5
10
  /**
6
11
  * The <f-template> custom element that will provide view logic to the element
7
12
  */
@@ -19,8 +24,9 @@ class TemplateElement extends FASTElement {
19
24
  const registeredFastElement = fastElementRegistry.getByType(value);
20
25
  const template = this.getElementsByTagName("template").item(0);
21
26
  if (template) {
22
- yield this.resolveAllPartials(this.innerHTML);
23
- const { strings, values } = yield this.resolveStringsAndValues(this.innerHTML);
27
+ const innerHTML = yield transformInnerHTML(this.innerHTML);
28
+ yield this.resolveAllPartials(innerHTML);
29
+ const { strings, values } = yield this.resolveStringsAndValues(innerHTML);
24
30
  if (registeredFastElement) {
25
31
  // all new elements will get the updated template
26
32
  registeredFastElement.template =
@@ -56,7 +62,15 @@ class TemplateElement extends FASTElement {
56
62
  * @param values - The interpreted values.
57
63
  */
58
64
  resolveTemplateOrBehavior(strings, values) {
59
- return ViewTemplate.create(strings, values, DOMPolicy.create());
65
+ return ViewTemplate.create(strings, values, DOMPolicy.create({
66
+ guards: {
67
+ aspects: {
68
+ [DOMAspect.property]: {
69
+ innerHTML: allow,
70
+ },
71
+ },
72
+ },
73
+ }));
60
74
  }
61
75
  /**
62
76
  * Resolve a template directive
@@ -1,9 +1,17 @@
1
- const openBinding = "{{";
2
- const closeBinding = "}}";
1
+ const openClientSideBinding = "{";
2
+ const closeClientSideBinding = "}";
3
+ const openContentBinding = "{{";
4
+ const closeContentBinding = "}}";
5
+ const openUnescapedBinding = "{{{";
6
+ const closeUnescapedBinding = "}}}";
3
7
  const openTagStart = "<f-";
4
8
  const tagEnd = ">";
5
9
  const closeTagStart = "</f-";
6
10
  const attributeDirectivePrefix = "f-";
11
+ const startInnerHTMLDiv = `<div :innerHTML="{{`;
12
+ const startInnerHTMLDivLength = startInnerHTMLDiv.length;
13
+ const endInnerHTMLDiv = `}}"></div>`;
14
+ const endInnerHTMLDivLength = endInnerHTMLDiv.length;
7
15
  /**
8
16
  * Get the index of the next matching tag
9
17
  * @param openingTagStartSlice - The slice starting from the opening tag
@@ -131,20 +139,57 @@ function getAttributeDirectiveDataBindingConfig(innerHTML, config) {
131
139
  function getContentDataBindingConfig(config) {
132
140
  return Object.assign(Object.assign({}, config), { subtype: "content" });
133
141
  }
142
+ function getIndexAndBindingTypeOfNextDataBindingBehavior(innerHTML) {
143
+ // {{{}}} binding
144
+ const openingUnescapedStartIndex = innerHTML.indexOf(openUnescapedBinding);
145
+ const closingUnescapedStartIndex = innerHTML.indexOf(closeUnescapedBinding);
146
+ // {{}} binding
147
+ const openingContentStartIndex = innerHTML.indexOf(openContentBinding);
148
+ const closingContentStartIndex = innerHTML.indexOf(closeContentBinding);
149
+ // {} binding
150
+ const openingClientStartIndex = innerHTML.indexOf(openClientSideBinding);
151
+ const closingClientStartIndex = innerHTML.indexOf(closeClientSideBinding);
152
+ if (openingUnescapedStartIndex !== -1 &&
153
+ openingUnescapedStartIndex <= openingContentStartIndex &&
154
+ openingUnescapedStartIndex <= openingClientStartIndex) {
155
+ // is unescaped {{{}}}
156
+ return {
157
+ openingStartIndex: openingUnescapedStartIndex,
158
+ closingStartIndex: closingUnescapedStartIndex,
159
+ bindingType: "unescaped",
160
+ };
161
+ }
162
+ else if (openingContentStartIndex !== -1 &&
163
+ openingContentStartIndex <= openingClientStartIndex) {
164
+ // is default {{}}
165
+ return {
166
+ openingStartIndex: openingContentStartIndex,
167
+ closingStartIndex: closingContentStartIndex,
168
+ bindingType: "default",
169
+ };
170
+ }
171
+ // is client {}
172
+ return {
173
+ openingStartIndex: openingClientStartIndex,
174
+ closingStartIndex: closingClientStartIndex,
175
+ bindingType: "client",
176
+ };
177
+ }
134
178
  /**
135
179
  * Get the next data binding
136
180
  * @param innerHTML - The innerHTML string to evaluate
137
181
  * @returns DataBindingBehaviorConfig - A configuration object
138
182
  */
139
183
  function getNextDataBindingBehavior(innerHTML) {
140
- const openingStartIndex = innerHTML.indexOf(openBinding);
141
- const closingStartIndex = innerHTML.indexOf(closeBinding);
184
+ const { openingStartIndex, closingStartIndex, bindingType } = getIndexAndBindingTypeOfNextDataBindingBehavior(innerHTML);
185
+ const bindingLength = bindingType === "client" ? 1 : bindingType === "default" ? 2 : 3;
142
186
  const partialConfig = {
143
187
  type: "dataBinding",
188
+ bindingType,
144
189
  openingStartIndex,
145
- openingEndIndex: openingStartIndex + 2,
190
+ openingEndIndex: openingStartIndex + bindingLength,
146
191
  closingStartIndex,
147
- closingEndIndex: closingStartIndex + 2,
192
+ closingEndIndex: closingStartIndex + bindingLength,
148
193
  };
149
194
  return isAttributeDirective(innerHTML, openingStartIndex)
150
195
  ? getAttributeDirectiveDataBindingConfig(innerHTML, partialConfig)
@@ -158,7 +203,7 @@ function getNextDataBindingBehavior(innerHTML) {
158
203
  * @returns DataBindingBehaviorConfig | DirectiveBehaviorConfig | null - A configuration object or null
159
204
  */
160
205
  export function getNextBehavior(innerHTML) {
161
- const dataBindingOpen = innerHTML.indexOf(openBinding);
206
+ const dataBindingOpen = innerHTML.indexOf(openClientSideBinding); // client side binding will capture all bindings starting with "{"
162
207
  const directiveBindingOpen = innerHTML.indexOf(openTagStart);
163
208
  if (dataBindingOpen === -1 && directiveBindingOpen === -1) {
164
209
  return null;
@@ -294,3 +339,32 @@ export function getOperator(value) {
294
339
  rightIsValue: null,
295
340
  };
296
341
  }
342
+ /**
343
+ * This is the transform utility for rationalizing declarative HTML syntax
344
+ * with bindings in the ViewTemplate
345
+ * @param innerHTML The innerHTML to transform
346
+ * @param index The index to start the current slice of HTML to evaluate
347
+ */
348
+ export function transformInnerHTML(innerHTML, index = 0) {
349
+ const sliceToEvaluate = innerHTML.slice(index);
350
+ const nextBinding = getNextBehavior(sliceToEvaluate);
351
+ let transformedInnerHTML = innerHTML;
352
+ if (nextBinding && nextBinding.type === "dataBinding") {
353
+ if (nextBinding.bindingType === "unescaped") {
354
+ transformedInnerHTML = `${innerHTML.slice(0, index)}${sliceToEvaluate.slice(0, nextBinding.openingStartIndex)}${startInnerHTMLDiv}${sliceToEvaluate.slice(nextBinding.openingStartIndex + 3, nextBinding.closingStartIndex)}${endInnerHTMLDiv}${sliceToEvaluate.slice(nextBinding.closingStartIndex + 3)}`;
355
+ return transformInnerHTML(transformedInnerHTML, index +
356
+ startInnerHTMLDivLength +
357
+ endInnerHTMLDivLength +
358
+ nextBinding.closingStartIndex -
359
+ 3);
360
+ }
361
+ else if (nextBinding.bindingType === "client") {
362
+ return transformInnerHTML(transformedInnerHTML, index + nextBinding.closingEndIndex);
363
+ }
364
+ return transformInnerHTML(transformedInnerHTML, index + nextBinding.closingStartIndex - 2);
365
+ }
366
+ else if (nextBinding) {
367
+ return transformInnerHTML(transformedInnerHTML, index + nextBinding.closingTagEndIndex);
368
+ }
369
+ return transformedInnerHTML;
370
+ }
@@ -1,6 +1,6 @@
1
1
  import { __awaiter } from "tslib";
2
2
  import { expect, test } from "@playwright/test";
3
- import { getNextBehavior, getAllPartials, getIndexOfNextMatchingTag, pathResolver } from "./utilities.js";
3
+ import { getNextBehavior, getAllPartials, getIndexOfNextMatchingTag, pathResolver, transformInnerHTML } from "./utilities.js";
4
4
  test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* () {
5
5
  test.describe("content", () => __awaiter(void 0, void 0, void 0, function* () {
6
6
  test("get the next content binding", () => __awaiter(void 0, void 0, void 0, function* () {
@@ -8,6 +8,7 @@ test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* ()
8
8
  const templateResult = getNextBehavior(innerHTML);
9
9
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.type).toEqual("dataBinding");
10
10
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.subtype).toEqual("content");
11
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.bindingType).toEqual("default");
11
12
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.openingStartIndex).toEqual(0);
12
13
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.openingEndIndex).toEqual(2);
13
14
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.closingStartIndex).toEqual(6);
@@ -21,21 +22,23 @@ test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* ()
21
22
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.type).toEqual("dataBinding");
22
23
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.subtype).toEqual("attribute");
23
24
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.aspect).toEqual(null);
25
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.bindingType).toEqual("default");
24
26
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.openingStartIndex).toEqual(13);
25
27
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.openingEndIndex).toEqual(15);
26
28
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.closingStartIndex).toEqual(19);
27
29
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.closingEndIndex).toEqual(21);
28
30
  }));
29
31
  test("get the next attribute event binding", () => __awaiter(void 0, void 0, void 0, function* () {
30
- const innerHTML = "<input @click=\"{{handleClick()}}\">";
32
+ const innerHTML = "<input @click=\"{handleClick()}\">";
31
33
  const templateResult = getNextBehavior(innerHTML);
32
34
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.type).toEqual("dataBinding");
33
35
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.subtype).toEqual("attribute");
34
36
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.aspect).toEqual("@");
37
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.bindingType).toEqual("client");
35
38
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.openingStartIndex).toEqual(15);
36
- expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.openingEndIndex).toEqual(17);
37
- expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.closingStartIndex).toEqual(30);
38
- expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.closingEndIndex).toEqual(32);
39
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.openingEndIndex).toEqual(16);
40
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.closingStartIndex).toEqual(29);
41
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.closingEndIndex).toEqual(30);
39
42
  }));
40
43
  }));
41
44
  test.describe("templates", () => __awaiter(void 0, void 0, void 0, function* () {
@@ -69,37 +72,40 @@ test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* ()
69
72
  }));
70
73
  test.describe("attributes", () => __awaiter(void 0, void 0, void 0, function* () {
71
74
  test("children directive", () => __awaiter(void 0, void 0, void 0, function* () {
72
- const innerHTML = "<ul f-children=\"{{list}}\"></ul>";
75
+ const innerHTML = "<ul f-children=\"{list}\"></ul>";
73
76
  const result = getNextBehavior(innerHTML);
74
77
  expect(result === null || result === void 0 ? void 0 : result.type).toEqual("dataBinding");
75
78
  expect(result === null || result === void 0 ? void 0 : result.subtype).toEqual("attributeDirective");
76
79
  expect(result === null || result === void 0 ? void 0 : result.name).toEqual("children");
80
+ expect(result === null || result === void 0 ? void 0 : result.bindingType).toEqual("client");
77
81
  expect(result === null || result === void 0 ? void 0 : result.openingStartIndex).toEqual(16);
78
- expect(result === null || result === void 0 ? void 0 : result.openingEndIndex).toEqual(18);
79
- expect(result === null || result === void 0 ? void 0 : result.closingStartIndex).toEqual(22);
80
- expect(result === null || result === void 0 ? void 0 : result.closingEndIndex).toEqual(24);
82
+ expect(result === null || result === void 0 ? void 0 : result.openingEndIndex).toEqual(17);
83
+ expect(result === null || result === void 0 ? void 0 : result.closingStartIndex).toEqual(21);
84
+ expect(result === null || result === void 0 ? void 0 : result.closingEndIndex).toEqual(22);
81
85
  }));
82
86
  test("slotted directive", () => __awaiter(void 0, void 0, void 0, function* () {
83
- const innerHTML = "<slot f-slotted=\"{{slottedNodes}}\"></slot>";
87
+ const innerHTML = "<slot f-slotted=\"{slottedNodes}\"></slot>";
84
88
  const result = getNextBehavior(innerHTML);
85
89
  expect(result === null || result === void 0 ? void 0 : result.type).toEqual("dataBinding");
86
90
  expect(result === null || result === void 0 ? void 0 : result.subtype).toEqual("attributeDirective");
87
91
  expect(result === null || result === void 0 ? void 0 : result.name).toEqual("slotted");
92
+ expect(result === null || result === void 0 ? void 0 : result.bindingType).toEqual("client");
88
93
  expect(result === null || result === void 0 ? void 0 : result.openingStartIndex).toEqual(17);
89
- expect(result === null || result === void 0 ? void 0 : result.openingEndIndex).toEqual(19);
90
- expect(result === null || result === void 0 ? void 0 : result.closingStartIndex).toEqual(31);
91
- expect(result === null || result === void 0 ? void 0 : result.closingEndIndex).toEqual(33);
94
+ expect(result === null || result === void 0 ? void 0 : result.openingEndIndex).toEqual(18);
95
+ expect(result === null || result === void 0 ? void 0 : result.closingStartIndex).toEqual(30);
96
+ expect(result === null || result === void 0 ? void 0 : result.closingEndIndex).toEqual(31);
92
97
  }));
93
98
  test("ref directive", () => __awaiter(void 0, void 0, void 0, function* () {
94
- const innerHTML = "<video f-ref=\"{{video}}\"></video>";
99
+ const innerHTML = "<video f-ref=\"{video}\"></video>";
95
100
  const result = getNextBehavior(innerHTML);
96
101
  expect(result === null || result === void 0 ? void 0 : result.type).toEqual("dataBinding");
97
102
  expect(result === null || result === void 0 ? void 0 : result.subtype).toEqual("attributeDirective");
98
103
  expect(result === null || result === void 0 ? void 0 : result.name).toEqual("ref");
104
+ expect(result === null || result === void 0 ? void 0 : result.bindingType).toEqual("client");
99
105
  expect(result === null || result === void 0 ? void 0 : result.openingStartIndex).toEqual(14);
100
- expect(result === null || result === void 0 ? void 0 : result.openingEndIndex).toEqual(16);
101
- expect(result === null || result === void 0 ? void 0 : result.closingStartIndex).toEqual(21);
102
- expect(result === null || result === void 0 ? void 0 : result.closingEndIndex).toEqual(23);
106
+ expect(result === null || result === void 0 ? void 0 : result.openingEndIndex).toEqual(15);
107
+ expect(result === null || result === void 0 ? void 0 : result.closingStartIndex).toEqual(20);
108
+ expect(result === null || result === void 0 ? void 0 : result.closingEndIndex).toEqual(21);
103
109
  }));
104
110
  }));
105
111
  test.describe("partials", () => __awaiter(void 0, void 0, void 0, function* () {
@@ -160,4 +166,21 @@ test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* ()
160
166
  expect(pathResolver("../foo")({}, { parent: { foo: "bar" } })).toEqual("bar");
161
167
  }));
162
168
  }));
169
+ test.describe("transformInnerHTML", () => __awaiter(void 0, void 0, void 0, function* () {
170
+ test("should resolve a single unescaped data binding", () => __awaiter(void 0, void 0, void 0, function* () {
171
+ expect(transformInnerHTML(`{{{html}}}`)).toEqual(`<div :innerHTML="{{html}}"></div>`);
172
+ }));
173
+ test("should resolve multiple unescaped data bindings", () => __awaiter(void 0, void 0, void 0, function* () {
174
+ expect(transformInnerHTML(`{{{foo}}}{{{bar}}}`)).toEqual(`<div :innerHTML="{{foo}}"></div><div :innerHTML="{{bar}}"></div>`);
175
+ }));
176
+ test("should resolve a unescaped data bindings in a mix of other data content bindings", () => __awaiter(void 0, void 0, void 0, function* () {
177
+ expect(transformInnerHTML(`{{text1}}{{{foo}}}{{text2}}{{{bar}}}{{text3}}`)).toEqual(`{{text1}}<div :innerHTML="{{foo}}"></div>{{text2}}<div :innerHTML="{{bar}}"></div>{{text3}}`);
178
+ }));
179
+ test("should resolve a unescaped data bindings in a mix of other data attribute bindings and nesting", () => __awaiter(void 0, void 0, void 0, function* () {
180
+ expect(transformInnerHTML(`<div data-foo="{{text1}}">{{{foo}}}</div><div data-bar="{{text2}}"></div>{{{bar}}}<div data-bat="{{text3}}"></div>`)).toEqual(`<div data-foo="{{text1}}"><div :innerHTML="{{foo}}"></div></div><div data-bar="{{text2}}"></div><div :innerHTML="{{bar}}"></div><div data-bat="{{text3}}"></div>`);
181
+ }));
182
+ test("should resolve a non-data and non-attribute bindings", () => __awaiter(void 0, void 0, void 0, function* () {
183
+ expect(transformInnerHTML(`<button @click="{handleNoArgsClick()}">No arguments</button>`)).toEqual(`<button @click="{handleNoArgsClick()}">No arguments</button>`);
184
+ }));
185
+ }));
163
186
  }));
@@ -14,4 +14,10 @@ test.describe("f-template", () => __awaiter(void 0, void 0, void 0, function* ()
14
14
  yield expect(yield customElement.getAttribute("text")).toEqual("Hello pluto");
15
15
  yield expect(customElement).toHaveText("Hello pluto");
16
16
  }));
17
+ test("create an unescaped binding", ({ page }) => __awaiter(void 0, void 0, void 0, function* () {
18
+ yield page.goto("/binding");
19
+ const customElement = yield page.locator("test-element-unescaped");
20
+ yield expect(yield customElement.locator("p").count()).toEqual(1);
21
+ yield expect(customElement).toHaveText("Hello world");
22
+ }));
17
23
  }));
@@ -14,6 +14,15 @@ __decorate([
14
14
  TestElement.define({
15
15
  name: "test-element",
16
16
  });
17
+ class TestElementUnescaped extends FASTElement {
18
+ constructor() {
19
+ super(...arguments);
20
+ this.html = `<p>Hello world</p>`;
21
+ }
22
+ }
23
+ TestElementUnescaped.define({
24
+ name: "test-element-unescaped",
25
+ });
17
26
  TemplateElement.define({
18
27
  name: "f-template",
19
28
  });
@@ -20,6 +20,10 @@ test.describe("f-template", () => __awaiter(void 0, void 0, void 0, function* ()
20
20
  "C"
21
21
  ];
22
22
  });
23
+ const timeout = new Promise(function (resolve) {
24
+ setTimeout(resolve, 100);
25
+ });
26
+ yield timeout;
23
27
  const listItemCount2 = yield page.evaluate(() => {
24
28
  var _a;
25
29
  const customElement = document.getElementsByTagName("test-element");
@@ -16,7 +16,10 @@ test.describe("f-template", () => __awaiter(void 0, void 0, void 0, function* ()
16
16
  "C"
17
17
  ];
18
18
  });
19
- yield expect(customElement).toHaveJSProperty("list", ["A", "B", "C"]);
19
+ const timeout = new Promise(function (resolve) {
20
+ setTimeout(resolve, 100);
21
+ });
22
+ yield timeout;
20
23
  customElementListItems = yield customElement.locator("li");
21
24
  expect(yield customElementListItems.count()).toEqual(3);
22
25
  expect(yield customElementListItems.nth(0).textContent()).toEqual("A - Bat");