Skip to content

Commit 6f15d5d

Browse files
committed
bug #588 [Live] Show a clear error multiple roots + resolve Component promise after render (weaverryan)
This PR was squashed before being merged into the 2.x branch. Discussion ---------- [Live] Show a clear error multiple roots + resolve Component promise after render | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | Tickets | Fix #501 | License | MIT Hi! A) Ignore "comment" or "text" nodes if they are at the "root" level B) Throw a clear error if the component has multiple elements (no re-render) C) Resolve the Component promise after re-rendering. This is how this was intended, it allows you to, for example, `await component.render()` and your code will continue only after the Ajax call and re-render have finished. Commits ------- 6faa252 [Live] Show a clear error multiple roots + resolve Component promise after render
2 parents 7147d5d + 6faa252 commit 6f15d5d

File tree

3 files changed

+42
-7
lines changed

3 files changed

+42
-7
lines changed

src/LiveComponent/assets/src/Component/index.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,6 @@ export default class Component {
300300

301301
this.backendRequest.promise.then(async (response) => {
302302
const backendResponse = new BackendResponse(response);
303-
thisPromiseResolve(backendResponse);
304303
const html = await backendResponse.getBody();
305304

306305
// if the response does not contain a component, render as an error
@@ -313,13 +312,17 @@ export default class Component {
313312
this.renderError(html);
314313
}
315314

315+
thisPromiseResolve(backendResponse);
316+
316317
return response;
317318
}
318319

319320
this.processRerender(html, backendResponse);
320-
321321
this.backendRequest = null;
322322

323+
// finally resolve this promise
324+
thisPromiseResolve(backendResponse);
325+
323326
// do we already have another request pending?
324327
if (this.isRequestPending) {
325328
this.isRequestPending = false;
@@ -364,7 +367,14 @@ export default class Component {
364367
modifiedModelValues[modelName] = this.valueStore.get(modelName);
365368
});
366369

367-
const newElement = htmlToElement(html);
370+
let newElement;
371+
try {
372+
newElement = htmlToElement(html);
373+
} catch (error) {
374+
console.error('There was a problem with the component HTML returned:');
375+
376+
throw error;
377+
}
368378
// normalize new element into non-loading state before diff
369379
this.hooks.triggerHook('loading.state:finished', newElement);
370380

src/LiveComponent/assets/src/dom_utils.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,14 +220,20 @@ export function htmlToElement(html: string): HTMLElement {
220220
html = html.trim();
221221
template.innerHTML = html;
222222

223-
const child = template.content.firstChild;
223+
if (template.content.childElementCount > 1) {
224+
throw new Error(
225+
`Component HTML contains ${template.content.childElementCount} elements, but only 1 root element is allowed.`
226+
);
227+
}
228+
229+
const child = template.content.firstElementChild;
224230
if (!child) {
225231
throw new Error('Child not found');
226232
}
227233

228234
// enforcing this for type simplicity: in practice, this is only use for HTMLElements
229235
if (!(child instanceof HTMLElement)) {
230-
throw new Error(`Created element is not an Element from HTML: ${html.trim()}`);
236+
throw new Error(`Created element is not an HTMLElement: ${html.trim()}`);
231237
}
232238

233239
return child;

src/LiveComponent/assets/test/controller/render.test.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -365,9 +365,8 @@ describe('LiveController rendering Tests', () => {
365365

366366
await waitFor(() => expect(test.element).toHaveTextContent('123'));
367367
});
368-
369368
it('can update html containing svg', async () => {
370-
const test = await createTest({ text: 'Hello' }, (data: any) => `
369+
const test = await createTest({text: 'Hello'}, (data: any) => `
371370
<div ${initComponent(data)}>
372371
${data.text}
373372
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="200">
@@ -389,4 +388,24 @@ describe('LiveController rendering Tests', () => {
389388

390389
await waitFor(() => expect(test.element).toHaveTextContent('123'));
391390
});
391+
392+
it('can understand comment in the response', async () => {
393+
const test = await createTest({ season: 'summer' }, (data: any) => `
394+
<!-- messy comment -->
395+
<div ${initComponent(data)}>
396+
The season is: ${data.season}
397+
</div>
398+
`);
399+
400+
test.expectsAjaxCall('get')
401+
.expectSentData(test.initialData)
402+
.serverWillChangeData((data) => {
403+
data.season = 'autumn';
404+
})
405+
.init();
406+
407+
await test.component.render();
408+
// verify the component *did* render ok
409+
expect(test.element).toHaveTextContent('The season is: autumn');
410+
});
392411
});

0 commit comments

Comments
 (0)