kopia lustrzana https://github.com/wagtail/wagtail
Allow TeleportController to have any number of child nodes of any kind
rodzic
a21b1cf21c
commit
2003e14a95
|
@ -89,9 +89,9 @@ describe('TeleportController', () => {
|
||||||
|
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
|
|
||||||
expect(document.getElementById('target-container').innerHTML).toEqual(
|
expect(
|
||||||
'<div id="content">Some content</div>',
|
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 () => {
|
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();
|
await Promise.resolve();
|
||||||
|
|
||||||
expect(document.getElementById('target-container').innerHTML).toEqual(
|
expect(
|
||||||
'<div id="content">Some content</div>',
|
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 () => {
|
it('should not clear the target container if the reset value is unset (false)', async () => {
|
||||||
|
@ -160,12 +160,84 @@ describe('TeleportController', () => {
|
||||||
|
|
||||||
await Promise.resolve();
|
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(
|
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 = [];
|
const errors = [];
|
||||||
|
|
||||||
document.getElementById('template').innerHTML = '';
|
document.getElementById('template').innerHTML = '';
|
||||||
|
@ -176,12 +248,10 @@ describe('TeleportController', () => {
|
||||||
|
|
||||||
await Promise.resolve(application.start());
|
await Promise.resolve(application.start());
|
||||||
|
|
||||||
expect(errors).toEqual([
|
expect(errors).toEqual([]);
|
||||||
{
|
|
||||||
error: new Error('Invalid template content.'),
|
const contents = document.getElementById('target-container').innerHTML;
|
||||||
message: 'Error connecting controller',
|
expect(contents).toEqual('');
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if a valid target container cannot be resolved', async () => {
|
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 = () => {
|
const complete = () => {
|
||||||
if (completed) return;
|
if (completed) return;
|
||||||
if (this.resetValue) target.innerHTML = '';
|
if (this.resetValue) target.innerHTML = '';
|
||||||
target.append(this.templateElement);
|
target.append(...this.templateFragment.childNodes);
|
||||||
this.dispatch('appended', { cancelable: false, detail: { target } });
|
this.dispatch('appended', { cancelable: false, detail: { target } });
|
||||||
completed = true;
|
completed = true;
|
||||||
if (this.keepValue) return;
|
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() {
|
get templateFragment() {
|
||||||
const templateElement =
|
const content = this.element.content;
|
||||||
this.element.content.firstElementChild?.cloneNode(true);
|
// 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)
|
||||||
if (!(templateElement instanceof HTMLElement)) {
|
const templateFragment = content.cloneNode(true) as typeof content;
|
||||||
throw new Error('Invalid template content.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// HACK:
|
// HACK:
|
||||||
// cloneNode doesn't run scripts, so we need to create new script elements
|
// 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
|
// and copy the attributes and innerHTML over. This is necessary when we're
|
||||||
// teleporting a template that contains legacy init code, e.g. initDateChooser.
|
// teleporting a template that contains legacy init code, e.g. initDateChooser.
|
||||||
// Only do this for inline scripts, as that's what we're expecting.
|
// Only do this for inline scripts, as that's what we're expecting.
|
||||||
templateElement
|
templateFragment
|
||||||
.querySelectorAll('script:not([src], [type])')
|
.querySelectorAll('script:not([src], [type])')
|
||||||
.forEach((script) => {
|
.forEach((script) => {
|
||||||
const newScript = document.createElement('script');
|
const newScript = document.createElement('script');
|
||||||
|
@ -114,6 +112,6 @@ export class TeleportController extends Controller<HTMLTemplateElement> {
|
||||||
script.replaceWith(newScript);
|
script.replaceWith(newScript);
|
||||||
});
|
});
|
||||||
|
|
||||||
return templateElement;
|
return templateFragment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue