@remote-dom/polyfill 1.4.6 → 1.5.0

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/cjs/ClipboardEvent.cjs +15 -0
  3. package/build/cjs/FocusEvent.cjs +15 -0
  4. package/build/cjs/ToggleEvent.cjs +16 -0
  5. package/build/cjs/Window.cjs +6 -0
  6. package/build/cjs/selectors.cjs +48 -13
  7. package/build/esm/ClipboardEvent.mjs +11 -0
  8. package/build/esm/FocusEvent.mjs +11 -0
  9. package/build/esm/ToggleEvent.mjs +12 -0
  10. package/build/esm/Window.mjs +6 -0
  11. package/build/esm/selectors.mjs +48 -13
  12. package/build/esnext/ClipboardEvent.esnext +11 -0
  13. package/build/esnext/FocusEvent.esnext +11 -0
  14. package/build/esnext/ToggleEvent.esnext +13 -0
  15. package/build/esnext/Window.esnext +6 -0
  16. package/build/esnext/selectors.esnext +43 -13
  17. package/build/tsconfig.tsbuildinfo +1 -1
  18. package/build/typescript/ClipboardEvent.d.ts +6 -0
  19. package/build/typescript/ClipboardEvent.d.ts.map +1 -0
  20. package/build/typescript/FocusEvent.d.ts +6 -0
  21. package/build/typescript/FocusEvent.d.ts.map +1 -0
  22. package/build/typescript/ToggleEvent.d.ts +7 -0
  23. package/build/typescript/ToggleEvent.d.ts.map +1 -0
  24. package/build/typescript/Window.d.ts +6 -0
  25. package/build/typescript/Window.d.ts.map +1 -1
  26. package/build/typescript/selectors.d.ts.map +1 -1
  27. package/build/typescript/tests/selectors.test.d.ts +2 -0
  28. package/build/typescript/tests/selectors.test.d.ts.map +1 -0
  29. package/package.json +1 -1
  30. package/source/ClipboardEvent.ts +12 -0
  31. package/source/FocusEvent.ts +12 -0
  32. package/source/ToggleEvent.ts +14 -0
  33. package/source/Window.ts +6 -0
  34. package/source/selectors.ts +52 -13
  35. package/source/tests/selectors.test.ts +280 -0
@@ -0,0 +1,280 @@
1
+ import {Window} from '../index.ts';
2
+ import {parseSelector} from '../selectors.ts';
3
+
4
+ import {describe, it, expect, beforeEach} from 'vitest';
5
+
6
+ describe('selector parsing and matching', () => {
7
+ beforeEach(() => {
8
+ const window = new Window();
9
+ Window.setGlobalThis(window);
10
+ });
11
+
12
+ describe('parseSelector', () => {
13
+ it('parses element selectors', () => {
14
+ const parts = parseSelector('div');
15
+ expect(parts).toHaveLength(1);
16
+ expect(parts[0]!.matchers).toHaveLength(1);
17
+ expect(parts[0]!.matchers[0]!).toMatchObject({
18
+ type: 1, // MatcherType.Element
19
+ name: 'div',
20
+ value: 'div',
21
+ });
22
+ });
23
+
24
+ it('parses ID selectors', () => {
25
+ const parts = parseSelector('#myid');
26
+ expect(parts).toHaveLength(1);
27
+ expect(parts[0]!.matchers[0]!).toMatchObject({
28
+ type: 2, // MatcherType.Id
29
+ name: 'myid',
30
+ value: 'myid',
31
+ });
32
+ });
33
+
34
+ it('parses class selectors', () => {
35
+ const parts = parseSelector('.myclass');
36
+ expect(parts).toHaveLength(1);
37
+ expect(parts[0]!.matchers[0]!).toMatchObject({
38
+ type: 3, // MatcherType.Class
39
+ name: 'myclass',
40
+ value: 'myclass',
41
+ });
42
+ });
43
+
44
+ it('parses attribute selectors without values', () => {
45
+ const parts = parseSelector('[disabled]');
46
+ expect(parts).toHaveLength(1);
47
+ expect(parts[0]!.matchers[0]!).toMatchObject({
48
+ type: 4, // MatcherType.Attribute
49
+ name: 'disabled',
50
+ value: undefined,
51
+ });
52
+ });
53
+
54
+ it('parses attribute selectors with values', () => {
55
+ const parts = parseSelector('[type="button"]');
56
+ expect(parts).toHaveLength(1);
57
+ expect(parts[0]!.matchers[0]!).toMatchObject({
58
+ type: 4, // MatcherType.Attribute
59
+ name: 'type',
60
+ value: 'button',
61
+ });
62
+ });
63
+
64
+ it('parses pseudo-class selectors', () => {
65
+ const parts = parseSelector(':hover');
66
+ expect(parts).toHaveLength(1);
67
+ expect(parts[0]!.matchers[0]!).toMatchObject({
68
+ type: 5, // MatcherType.Pseudo
69
+ name: 'hover',
70
+ value: undefined,
71
+ });
72
+ });
73
+
74
+ it('parses function selectors', () => {
75
+ const parts = parseSelector(':has(div)');
76
+ expect(parts).toHaveLength(1);
77
+ expect(parts[0]!.matchers[0]!).toMatchObject({
78
+ type: 6, // MatcherType.Function
79
+ name: 'has',
80
+ value: 'div',
81
+ });
82
+ });
83
+
84
+ it('parses :not() function selectors', () => {
85
+ const parts = parseSelector(':not(.hidden)');
86
+ expect(parts).toHaveLength(1);
87
+ expect(parts[0]!.matchers[0]!).toMatchObject({
88
+ type: 6, // MatcherType.Function
89
+ name: 'not',
90
+ value: '.hidden',
91
+ });
92
+ });
93
+
94
+ it('parses compound selectors', () => {
95
+ const parts = parseSelector('div.myclass#myid[type="button"]');
96
+ expect(parts).toHaveLength(1);
97
+ expect(parts[0]!.matchers).toHaveLength(4);
98
+ expect(parts[0]!.matchers[0]!.name).toBe('div');
99
+ expect(parts[0]!.matchers[1]!.name).toBe('myclass');
100
+ expect(parts[0]!.matchers[2]!.name).toBe('myid');
101
+ expect(parts[0]!.matchers[3]!.name).toBe('type');
102
+ });
103
+
104
+ it('parses child combinator', () => {
105
+ const parts = parseSelector('div > span');
106
+ expect(parts).toHaveLength(2);
107
+ expect(parts[0]!.combinator).toBe(1); // Combinator.Child
108
+ expect(parts[0]!.matchers[0]!.name).toBe('div');
109
+ expect(parts[1]!.matchers[0]!.name).toBe('span');
110
+ });
111
+
112
+ it('parses descendant combinator', () => {
113
+ const parts = parseSelector('div span');
114
+ expect(parts).toHaveLength(2);
115
+ expect(parts[0]!.combinator).toBe(0); // Combinator.Descendant
116
+ expect(parts[0]!.matchers[0]!.name).toBe('div');
117
+ expect(parts[1]!.matchers[0]!.name).toBe('span');
118
+ });
119
+
120
+ it('parses adjacent sibling combinator', () => {
121
+ const parts = parseSelector('h1 + p');
122
+ expect(parts).toHaveLength(2);
123
+ expect(parts[0]!.combinator).toBe(3); // Combinator.Adjacent
124
+ expect(parts[0]!.matchers[0]!.name).toBe('h1');
125
+ expect(parts[1]!.combinator).toBe(4); // Combinator.Inner
126
+ expect(parts[1]!.matchers[0]!.name).toBe('p');
127
+ });
128
+
129
+ it('parses general sibling combinator', () => {
130
+ const parts = parseSelector('h1 ~ p');
131
+ expect(parts).toHaveLength(2);
132
+ expect(parts[0]!.combinator).toBe(2); // Combinator.Sibling
133
+ expect(parts[0]!.matchers[0]!.name).toBe('h1');
134
+ expect(parts[1]!.combinator).toBe(4); // Combinator.Inner
135
+ expect(parts[1]!.matchers[0]!.name).toBe('p');
136
+ });
137
+
138
+ it('parses complex selectors', () => {
139
+ const parts = parseSelector('article > .header + .content:not(.hidden)');
140
+ expect(parts).toHaveLength(3);
141
+ expect(parts[0]!.combinator).toBe(1); // Child
142
+ expect(parts[1]!.combinator).toBe(3); // Adjacent
143
+ expect(parts[2]!.combinator).toBe(4); // Inner
144
+ expect(parts[0]!.matchers[0]!.name).toBe('article');
145
+ expect(parts[1]!.matchers[0]!.name).toBe('header');
146
+ expect(parts[2]!.matchers).toHaveLength(2);
147
+ expect(parts[2]!.matchers[0]!.name).toBe('content');
148
+ expect(parts[2]!.matchers[1]!.name).toBe('not');
149
+ });
150
+ });
151
+
152
+ describe('querySelector and querySelectorAll', () => {
153
+ let container: Element;
154
+
155
+ beforeEach(() => {
156
+ container = document.createElement('div');
157
+ container.innerHTML = `
158
+ <article class="post" id="main-post">
159
+ <h1 class="title">Main Title</h1>
160
+ <div class="content">
161
+ <p class="text">First paragraph</p>
162
+ <p class="text hidden">Hidden paragraph</p>
163
+ <span class="highlight">Highlighted text</span>
164
+ </div>
165
+ <aside class="sidebar">
166
+ <ul class="nav">
167
+ <li><a href="#" class="link active">Active Link</a></li>
168
+ <li><a href="#" class="link">Regular Link</a></li>
169
+ </ul>
170
+ </aside>
171
+ </article>
172
+ <footer class="footer">
173
+ <div class="content">
174
+ <p class="text">Footer text</p>
175
+ </div>
176
+ </footer>
177
+ `;
178
+ });
179
+
180
+ it('selects by element name', () => {
181
+ const articles = container.querySelectorAll('article');
182
+ expect(articles).toHaveLength(1);
183
+ expect(articles[0]!.getAttribute('class')).toBe('post');
184
+
185
+ const paragraphs = container.querySelectorAll('p');
186
+ expect(paragraphs).toHaveLength(3);
187
+ });
188
+
189
+ it('selects by ID', () => {
190
+ const main = container.querySelector('#main-post');
191
+ expect(main?.tagName.toLowerCase()).toBe('article');
192
+ });
193
+
194
+ it('selects by class', () => {
195
+ const texts = container.querySelectorAll('.text');
196
+ expect(texts).toHaveLength(3);
197
+
198
+ const hidden = container.querySelector('.hidden');
199
+ expect(hidden?.textContent?.trim()).toBe('Hidden paragraph');
200
+ });
201
+
202
+ it('selects by attribute', () => {
203
+ const links = container.querySelectorAll('[href]');
204
+ expect(links).toHaveLength(2);
205
+
206
+ const activeLinks = container.querySelectorAll('[href="#"]');
207
+ expect(activeLinks).toHaveLength(2);
208
+ });
209
+
210
+ it('selects by compound selectors', () => {
211
+ const activeLink = container.querySelector('a.link.active');
212
+ expect(activeLink?.textContent?.trim()).toBe('Active Link');
213
+
214
+ const hiddenText = container.querySelector('p.text.hidden');
215
+ expect(hiddenText?.textContent?.trim()).toBe('Hidden paragraph');
216
+ });
217
+
218
+ it('selects with descendant combinator', () => {
219
+ const contentTexts = container.querySelectorAll('.content p');
220
+ expect(contentTexts).toHaveLength(3);
221
+
222
+ const sidebarLinks = container.querySelectorAll('.sidebar a');
223
+ expect(sidebarLinks).toHaveLength(2);
224
+ });
225
+
226
+ it('selects with child combinator', () => {
227
+ const directContentChildren = container.querySelectorAll('.content > p');
228
+ expect(directContentChildren).toHaveLength(3);
229
+
230
+ const directArticleChildren = container.querySelectorAll('article > h1');
231
+ expect(directArticleChildren).toHaveLength(1);
232
+ });
233
+
234
+ it('selects with adjacent sibling combinator', () => {
235
+ const titleSibling = container.querySelector('h1 + div');
236
+ expect(titleSibling?.getAttribute('class')).toBe('content');
237
+ });
238
+
239
+ it('selects with general sibling combinator', () => {
240
+ const titleSiblings = container.querySelectorAll('h1 ~ div');
241
+ expect(titleSiblings).toHaveLength(1);
242
+
243
+ const contentSiblings = container.querySelectorAll('.content ~ aside');
244
+ expect(contentSiblings).toHaveLength(1);
245
+ });
246
+
247
+ it('selects with :has() pseudo-class', () => {
248
+ const hasLinks = container.querySelectorAll(':has(a)');
249
+ expect(hasLinks.length).toBeGreaterThan(0);
250
+
251
+ const hasActiveLink = container.querySelector(':has(.active)');
252
+ expect(hasActiveLink).toBeTruthy();
253
+ });
254
+
255
+ it('handles complex selectors', () => {
256
+ const complexSelector = container.querySelectorAll(
257
+ 'article .content > p.text',
258
+ );
259
+ expect(complexSelector).toHaveLength(2);
260
+
261
+ const deepSelector = container.querySelector('.sidebar ul li a.active');
262
+ expect(deepSelector?.textContent?.trim()).toBe('Active Link');
263
+ });
264
+
265
+ it('returns null/empty for non-matching selectors', () => {
266
+ expect(container.querySelector('.nonexistent')).toBeNull();
267
+ expect(container.querySelectorAll('.nonexistent')).toHaveLength(0);
268
+
269
+ expect(container.querySelector('table')).toBeNull();
270
+ expect(container.querySelector('#nonexistent-id')).toBeNull();
271
+ });
272
+
273
+ it('handles edge cases', () => {
274
+ expect(container.querySelectorAll('')).toHaveLength(0);
275
+
276
+ const allElements = container.querySelectorAll('*');
277
+ expect(allElements.length).toBeGreaterThan(0);
278
+ });
279
+ });
280
+ });