Skip to content

Commit

Permalink
Merge pull request #10 from chialab/svelte5
Browse files Browse the repository at this point in the history
Svelte5
  • Loading branch information
edoardocavazza authored Jun 17, 2024
2 parents 595f097 + 9303e09 commit 64fdcdf
Show file tree
Hide file tree
Showing 7 changed files with 2,299 additions and 2,029 deletions.
5 changes: 5 additions & 0 deletions .changeset/tame-ligers-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chialab/quantum': minor
---

Extend `CharacterData` prototype.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"@chialab/eslint-config": "^4.0.3",
"@chialab/prettier-config": "^1.2.2",
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@testing-library/svelte": "^4.0.3",
"@testing-library/svelte": "^5.1.0",
"@vitest/browser": "^1.0.0",
"@vitest/coverage-istanbul": "^1.2.1",
"eslint": "^8.44.0",
Expand All @@ -56,7 +56,7 @@
"publint": "^0.1.15",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"svelte": "^4.0.2",
"svelte": "^5.0.0-next.0",
"typescript": "^5.0.2",
"uhtml": "^3.2.1",
"vite": "^5.0.0",
Expand Down
116 changes: 116 additions & 0 deletions src/CharacterData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { getParentRealm } from './Realm.js';
import { defineProperty, getOwnPropertyDescriptors } from './utils.js';

/**
* Extends the CharacterData prototype with realm aware methods.
* @param {typeof CharacterData} CharacterData The CharacterData constructor to extend.
*/
export function extendCharacterData(CharacterData) {
const CharacterDataPrototype = CharacterData.prototype;
const { remove, previousElementSibling, nextElementSibling, after, before, replaceWith } =
getOwnPropertyDescriptors(CharacterDataPrototype);

defineProperty(CharacterDataPrototype, 'remove', {
/**
* @this {Element}
*/
value() {
const parentRealm = getParentRealm(this, true);
if (!parentRealm) {
return /** @type {import('./utils.js').ValueDescriptor} */ (remove).value.call(this);
}
return parentRealm.remove(this);
},
});

defineProperty(CharacterDataPrototype, 'previousElementSibling', {
/**
* @this {Element}
*/
get() {
const parentRealm = getParentRealm(this);
if (!parentRealm) {
return (
/** @type {import('./utils.js').GetterDescriptor} */ (previousElementSibling).get.call(this) ?? null
);
}

let sibling = parentRealm.getPreviousSibling(this);
while (sibling) {
if (sibling.nodeType === 1) {
return sibling;
}
sibling = parentRealm.getPreviousSibling(sibling);
}
return null;
},
set: previousElementSibling.set,
});

defineProperty(CharacterDataPrototype, 'nextElementSibling', {
/**
* @this {Element}
*/
get() {
const parentRealm = getParentRealm(this);
if (!parentRealm) {
return /** @type {import('./utils.js').GetterDescriptor} */ (nextElementSibling).get.call(this) ?? null;
}

let sibling = parentRealm.getNextSibling(this);
while (sibling) {
if (sibling.nodeType === 1) {
return sibling;
}
sibling = parentRealm.getNextSibling(sibling);
}
return null;
},
set: nextElementSibling.set,
});
defineProperty(CharacterDataPrototype, 'after', {
/**
* @this {Element}
* @param {(ChildNode | string)[]} nodes
*/
value(...nodes) {
const parentRealm = getParentRealm(this, true);
if (!parentRealm) {
return /** @type {import('./utils.js').ValueDescriptor} */ (after).value.apply(this, nodes);
}
const sibling = parentRealm.getNextSibling(this);
if (sibling) {
return parentRealm.insertBefore(sibling, ...nodes);
}
return parentRealm.append(...nodes);
},
});

defineProperty(CharacterDataPrototype, 'before', {
/**
* @this {Element}
* @param {(ChildNode | string)[]} nodes
*/
value(...nodes) {
const parentRealm = getParentRealm(this, true);
if (!parentRealm) {
return /** @type {import('./utils.js').ValueDescriptor} */ (before).value.apply(this, nodes);
}
return parentRealm.insertBefore(this, ...nodes);
},
});

defineProperty(CharacterDataPrototype, 'replaceWith', {
/**
* @this {Element}
* @param {(ChildNode | string)[]} nodes
*/
value(...nodes) {
const parentRealm = getParentRealm(this, true);
if (!parentRealm) {
return /** @type {import('./utils.js').ValueDescriptor} */ (replaceWith).value.apply(this, nodes);
}
return parentRealm.replaceWith(this, ...nodes);
},
});
}
2 changes: 2 additions & 0 deletions src/extend.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { extendCharacterData } from './CharacterData.js';
import { extendElement } from './Element.js';
import { extendNode } from './Node.js';
import { extendTreeWalker } from './TreeWalker.js';
Expand All @@ -9,6 +10,7 @@ import { extendTreeWalker } from './TreeWalker.js';
export function extend(window) {
extendNode(window.Node);
extendElement(window.Element);
extendCharacterData(window.CharacterData);
if (typeof window.TreeWalker !== 'undefined') {
extendTreeWalker(window.TreeWalker, window.NodeFilter);
}
Expand Down
54 changes: 23 additions & 31 deletions tests/svelte.spec.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, expect, test } from 'vitest';
import { render } from '@testing-library/svelte';
import { describe, expect, test } from 'vitest';
import Test1 from './components/Test1.svelte';
import Test2 from './components/Test2.svelte';
import Test3 from './components/Test3.svelte';

describe('Svelte', () => {
afterEach(cleanup);

test('should update text content', async () => {
const body = document.createElement('div');
const { component } = render(
const { container, rerender } = render(
Test1,
{
props: {
text: 'Text',
},
},
{
container: body,
container: document.createElement('div'),
}
);

const container = body.children[0];
const element = container.children[0];
const textNode = element.childNodes[0];
element.connectedCallback();
Expand All @@ -32,90 +28,86 @@ describe('Svelte', () => {
expect(textNode.parentNode).toBe(element);
expect(textNode.textContent).toBe('Text');
expect(container.innerHTML).toBe(
'<custom-element><span>Text<!--isµ0--></span><div><!--isµ1--></div></custom-element><!--<Test1>-->'
'<custom-element><span>Text<!--isµ0--></span><div><!--isµ1--></div></custom-element>'
);

await component.$set({ text: 'Update' });
await rerender({ text: 'Update' });

expect(container.childNodes.length).toBe(2);
expect(container.children[0]).toBe(element);
expect(element.childNodes.length).toBe(1);
expect(textNode.textContent).toBe('Update');
expect(container.innerHTML).toBe(
'<custom-element><span>Update<!--isµ0--></span><div><!--isµ1--></div></custom-element><!--<Test1>-->'
'<custom-element><span>Update<!--isµ0--></span><div><!--isµ1--></div></custom-element>'
);
});

test('should update text content with multiple text nodes', async () => {
const body = document.createElement('div');
const { component } = render(
const { container, rerender } = render(
Test2,
{
props: {
text: 'Text',
},
},
{
container: body,
container: document.createElement('div'),
}
);

const container = body.children[0];
const element = container.children[0];
const textNode = element.childNodes[0];
element.connectedCallback();

expect(container.childNodes.length).toBe(2);
expect(element.parentNode).toBe(container);
expect(element.childNodes.length).toBe(3);
expect(element.childNodes.length).toBe(1);
expect(textNode.parentNode).toBe(element);
expect(textNode.textContent).toBe('Text');
expect(textNode.textContent).toBe('Text children');
expect(container.innerHTML).toBe(
'<custom-element><span>Text children<!--isµ0--></span><div><!--isµ1--></div></custom-element><!--<Test2>-->'
'<custom-element><span>Text children<!--isµ0--></span><div><!--isµ1--></div></custom-element>'
);

await component.$set({ text: 'Update' });
await rerender({ text: 'Update' });

expect(container.childNodes.length).toBe(2);
expect(container.children[0]).toBe(element);
expect(element.childNodes.length).toBe(3);
expect(textNode.textContent).toBe('Update');
expect(element.childNodes.length).toBe(1);
expect(textNode.textContent).toBe('Update children');
expect(container.innerHTML.replace(/\n\s+/g, ' ')).toBe(
'<custom-element><span>Update children<!--isµ0--></span><div><!--isµ1--></div></custom-element><!--<Test2>-->'
'<custom-element><span>Update children<!--isµ0--></span><div><!--isµ1--></div></custom-element>'
);
});

test('should update named slots', async () => {
const body = document.createElement('div');
const { component } = render(
const { container, rerender } = render(
Test3,
{
props: {
title: true,
},
},
{
container: body,
container: document.createElement('div'),
}
);
const container = body.children[0];
const element = container.children[0];
const textNode = element.childNodes[0];
const lastNode = element.childNodes[2];
element.connectedCallback();

expect(element.childNodes.length).toBe(3);
expect(element.childNodes.length).toBe(4);
expect(container.innerHTML.replace(/\n\s+/g, ' ')).toBe(
'<custom-element><span>Text end<!--isµ0--></span><div><h1 slot="children">Title</h1><!--isµ1--></div></custom-element><!--<Test3>-->'
'<custom-element><span>Text <!----> end<!--isµ0--></span><div><h1 slot="children">Title</h1><!--isµ1--></div></custom-element>'
);

await component.$set({ title: false });
await rerender({ title: false });

expect(element.childNodes.length).toBe(3);
expect(element.childNodes.length).toBe(4);
expect(element.childNodes[0]).toBe(textNode);
expect(element.childNodes[2]).toBe(lastNode);
expect(container.innerHTML.replace(/\n\s+/g, ' ')).toBe(
'<custom-element><span>Text end<!--isµ0--></span><div><h2 slot="children">Subitle</h2><!--isµ1--></div></custom-element><!--<Test3>-->'
'<custom-element><span>Text <!----> end<!--isµ0--></span><div><h2 slot="children">Subitle</h2><!--isµ1--></div></custom-element>'
);
});
});
6 changes: 5 additions & 1 deletion vite.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { svelte } from '@sveltejs/vite-plugin-svelte';
import { svelteTesting } from '@testing-library/svelte/vite';
import { defineConfig } from 'vite';

export default defineConfig({
plugins: [svelte()],
plugins: [svelte(), svelteTesting()],
test: {
browser: {
name: 'chrome',
Expand All @@ -13,5 +14,8 @@ export default defineConfig({
provider: 'istanbul',
include: ['src'],
},
alias: {
'@testing-library/svelte': '@testing-library/svelte/svelte5',
},
},
});
Loading

0 comments on commit 64fdcdf

Please sign in to comment.