mirror of
https://github.com/wagtail/wagtail.git
synced 2024-11-25 05:02:57 +01:00
Allow TeleportController to have any number of child nodes of any kind
This commit is contained in:
parent
a21b1cf21c
commit
2003e14a95
@ -89,9 +89,9 @@ describe('TeleportController', () => {
|
||||
|
||||
await Promise.resolve();
|
||||
|
||||
expect(document.getElementById('target-container').innerHTML).toEqual(
|
||||
'<div id="content">Some content</div>',
|
||||
);
|
||||
expect(
|
||||
document.getElementById('target-container').innerHTML.trim(),
|
||||
).toEqual('<div id="content">Some content</div>');
|
||||
});
|
||||
|
||||
it('should allow for a default target container within the root element of a shadow DOM', async () => {
|
||||
@ -136,9 +136,9 @@ describe('TeleportController', () => {
|
||||
|
||||
await Promise.resolve();
|
||||
|
||||
expect(document.getElementById('target-container').innerHTML).toEqual(
|
||||
'<div id="content">Some content</div>',
|
||||
);
|
||||
expect(
|
||||
document.getElementById('target-container').innerHTML.trim(),
|
||||
).toEqual('<div id="content">Some content</div>');
|
||||
});
|
||||
|
||||
it('should not clear the target container if the reset value is unset (false)', async () => {
|
||||
@ -160,12 +160,84 @@ describe('TeleportController', () => {
|
||||
|
||||
await Promise.resolve();
|
||||
|
||||
const contents = document.getElementById('target-container').innerHTML;
|
||||
expect(contents).toContain('<p>I should still be here</p>');
|
||||
expect(contents).toContain('<div id="content">Some content</div>');
|
||||
});
|
||||
|
||||
it('should allow the template to contain multiple children', async () => {
|
||||
document.body.innerHTML += `
|
||||
<div id="target-container"></div>
|
||||
`;
|
||||
|
||||
const template = document.querySelector('template');
|
||||
template.setAttribute(
|
||||
'data-w-teleport-target-value',
|
||||
'#target-container',
|
||||
);
|
||||
|
||||
const otherTemplateContent = document.createElement('div');
|
||||
otherTemplateContent.innerHTML = 'Other content';
|
||||
otherTemplateContent.id = 'other-content';
|
||||
template.content.appendChild(otherTemplateContent);
|
||||
|
||||
expect(document.getElementById('target-container').innerHTML).toEqual('');
|
||||
|
||||
application.start();
|
||||
|
||||
await Promise.resolve();
|
||||
|
||||
const container = document.getElementById('target-container');
|
||||
const content = container.querySelector('#content');
|
||||
const otherContent = container.querySelector('#other-content');
|
||||
expect(content).not.toBeNull();
|
||||
expect(otherContent).not.toBeNull();
|
||||
expect(content.innerHTML.trim()).toEqual('Some content');
|
||||
expect(otherContent.innerHTML.trim()).toEqual('Other content');
|
||||
});
|
||||
|
||||
it('should not throw an error if the template content is empty', async () => {
|
||||
document.body.innerHTML += `
|
||||
<div id="target-container"><p>I should still be here</p></div>
|
||||
`;
|
||||
|
||||
const template = document.querySelector('template');
|
||||
template.setAttribute(
|
||||
'data-w-teleport-target-value',
|
||||
'#target-container',
|
||||
);
|
||||
|
||||
expect(document.getElementById('target-container').innerHTML).toEqual(
|
||||
'<p>I should still be here</p><div id="content">Some content</div>',
|
||||
'<p>I should still be here</p>',
|
||||
);
|
||||
|
||||
const errors = [];
|
||||
|
||||
document.getElementById('template').innerHTML = '';
|
||||
application.handleError = (error, message) => {
|
||||
errors.push({ error, message });
|
||||
};
|
||||
|
||||
await Promise.resolve(application.start());
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
|
||||
expect(document.getElementById('target-container').innerHTML).toEqual(
|
||||
'<p>I should still be here</p>',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the template content is empty', async () => {
|
||||
it('should allow erasing the target container by using an empty template with reset value set to true', async () => {
|
||||
document.body.innerHTML += `
|
||||
<div id="target-container"><p>I should not be here</p></div>
|
||||
`;
|
||||
|
||||
const template = document.querySelector('template');
|
||||
template.setAttribute(
|
||||
'data-w-teleport-target-value',
|
||||
'#target-container',
|
||||
);
|
||||
template.setAttribute('data-w-teleport-reset-value', 'true');
|
||||
const errors = [];
|
||||
|
||||
document.getElementById('template').innerHTML = '';
|
||||
@ -176,12 +248,10 @@ describe('TeleportController', () => {
|
||||
|
||||
await Promise.resolve(application.start());
|
||||
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
error: new Error('Invalid template content.'),
|
||||
message: 'Error connecting controller',
|
||||
},
|
||||
]);
|
||||
expect(errors).toEqual([]);
|
||||
|
||||
const contents = document.getElementById('target-container').innerHTML;
|
||||
expect(contents).toEqual('');
|
||||
});
|
||||
|
||||
it('should throw an error if a valid target container cannot be resolved', async () => {
|
||||
|
@ -46,7 +46,7 @@ export class TeleportController extends Controller<HTMLTemplateElement> {
|
||||
const complete = () => {
|
||||
if (completed) return;
|
||||
if (this.resetValue) target.innerHTML = '';
|
||||
target.append(this.templateElement);
|
||||
target.append(...this.templateFragment.childNodes);
|
||||
this.dispatch('appended', { cancelable: false, detail: { target } });
|
||||
completed = true;
|
||||
if (this.keepValue) return;
|
||||
@ -88,22 +88,20 @@ export class TeleportController extends Controller<HTMLTemplateElement> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a valid HTMLElement from the controlled element's children.
|
||||
* Returns a fresh copy of the DocumentFragment from the controlled element.
|
||||
*/
|
||||
get templateElement() {
|
||||
const templateElement =
|
||||
this.element.content.firstElementChild?.cloneNode(true);
|
||||
|
||||
if (!(templateElement instanceof HTMLElement)) {
|
||||
throw new Error('Invalid template content.');
|
||||
}
|
||||
get templateFragment() {
|
||||
const content = this.element.content;
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode (returns the same type)
|
||||
// https://github.com/microsoft/TypeScript/issues/283 (TypeScript will return as Node, incorrectly)
|
||||
const templateFragment = content.cloneNode(true) as typeof content;
|
||||
|
||||
// HACK:
|
||||
// cloneNode doesn't run scripts, so we need to create new script elements
|
||||
// and copy the attributes and innerHTML over. This is necessary when we're
|
||||
// teleporting a template that contains legacy init code, e.g. initDateChooser.
|
||||
// Only do this for inline scripts, as that's what we're expecting.
|
||||
templateElement
|
||||
templateFragment
|
||||
.querySelectorAll('script:not([src], [type])')
|
||||
.forEach((script) => {
|
||||
const newScript = document.createElement('script');
|
||||
@ -114,6 +112,6 @@ export class TeleportController extends Controller<HTMLTemplateElement> {
|
||||
script.replaceWith(newScript);
|
||||
});
|
||||
|
||||
return templateElement;
|
||||
return templateFragment;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user