zero-query 0.5.2 → 0.7.5
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.
- package/README.md +12 -10
- package/cli/commands/build.js +7 -5
- package/cli/commands/bundle.js +286 -8
- package/cli/commands/dev/index.js +82 -0
- package/cli/commands/dev/logger.js +70 -0
- package/cli/commands/dev/overlay.js +366 -0
- package/cli/commands/dev/server.js +158 -0
- package/cli/commands/dev/validator.js +94 -0
- package/cli/commands/dev/watcher.js +147 -0
- package/cli/scaffold/favicon.ico +0 -0
- package/cli/scaffold/index.html +1 -0
- package/cli/scaffold/scripts/app.js +15 -22
- package/cli/scaffold/scripts/components/about.js +14 -2
- package/cli/scaffold/scripts/components/contacts/contacts.css +0 -7
- package/cli/scaffold/scripts/components/contacts/contacts.html +8 -7
- package/cli/scaffold/scripts/components/contacts/contacts.js +17 -1
- package/cli/scaffold/scripts/components/counter.js +30 -10
- package/cli/scaffold/scripts/components/home.js +3 -3
- package/cli/scaffold/scripts/components/todos.js +6 -5
- package/cli/scaffold/styles/styles.css +1 -0
- package/cli/utils.js +111 -6
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +2005 -216
- package/dist/zquery.min.js +3 -13
- package/index.d.ts +149 -1080
- package/index.js +18 -7
- package/package.json +9 -3
- package/src/component.js +186 -45
- package/src/core.js +327 -35
- package/src/diff.js +280 -0
- package/src/errors.js +155 -0
- package/src/expression.js +806 -0
- package/src/http.js +18 -10
- package/src/reactive.js +29 -4
- package/src/router.js +59 -6
- package/src/ssr.js +224 -0
- package/src/store.js +24 -8
- package/tests/component.test.js +304 -0
- package/tests/core.test.js +726 -0
- package/tests/diff.test.js +194 -0
- package/tests/errors.test.js +162 -0
- package/tests/expression.test.js +334 -0
- package/tests/http.test.js +181 -0
- package/tests/reactive.test.js +191 -0
- package/tests/router.test.js +332 -0
- package/tests/store.test.js +253 -0
- package/tests/utils.test.js +353 -0
- package/types/collection.d.ts +368 -0
- package/types/component.d.ts +210 -0
- package/types/errors.d.ts +103 -0
- package/types/http.d.ts +81 -0
- package/types/misc.d.ts +166 -0
- package/types/reactive.d.ts +76 -0
- package/types/router.d.ts +132 -0
- package/types/ssr.d.ts +49 -0
- package/types/store.d.ts +107 -0
- package/types/utils.d.ts +142 -0
- /package/cli/commands/{dev.js → dev.old.js} +0 -0
|
@@ -0,0 +1,726 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { query, queryAll, ZQueryCollection } from '../src/core.js';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Setup
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
document.body.innerHTML = `
|
|
10
|
+
<div id="main">
|
|
11
|
+
<p class="text first-p">Hello</p>
|
|
12
|
+
<p class="text second-p">World</p>
|
|
13
|
+
<span class="other">Span</span>
|
|
14
|
+
<p class="text third-p">Extra</p>
|
|
15
|
+
</div>
|
|
16
|
+
<div id="sidebar">
|
|
17
|
+
<ul id="nav">
|
|
18
|
+
<li class="nav-item active">Home</li>
|
|
19
|
+
<li class="nav-item">About</li>
|
|
20
|
+
<li class="nav-item">Contact</li>
|
|
21
|
+
</ul>
|
|
22
|
+
</div>
|
|
23
|
+
`;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// query() — single selector
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
describe('query()', () => {
|
|
32
|
+
it('returns ZQueryCollection by CSS selector', () => {
|
|
33
|
+
const col = query('#main');
|
|
34
|
+
expect(col).toBeInstanceOf(ZQueryCollection);
|
|
35
|
+
expect(col.first()).toBe(document.getElementById('main'));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('returns empty collection for non-matching selector', () => {
|
|
39
|
+
const col = query('#nonexistent');
|
|
40
|
+
expect(col).toBeInstanceOf(ZQueryCollection);
|
|
41
|
+
expect(col.length).toBe(0);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('returns empty collection for null/undefined', () => {
|
|
45
|
+
expect(query(null).length).toBe(0);
|
|
46
|
+
expect(query(undefined).length).toBe(0);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('wraps DOM element in collection', () => {
|
|
50
|
+
const div = document.createElement('div');
|
|
51
|
+
const col = query(div);
|
|
52
|
+
expect(col).toBeInstanceOf(ZQueryCollection);
|
|
53
|
+
expect(col.first()).toBe(div);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('creates elements from HTML string as collection', () => {
|
|
57
|
+
const col = query('<span>hello</span>');
|
|
58
|
+
expect(col).toBeInstanceOf(ZQueryCollection);
|
|
59
|
+
expect(col.first().tagName).toBe('SPAN');
|
|
60
|
+
expect(col.first().textContent).toBe('hello');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('returns all matching elements', () => {
|
|
64
|
+
const col = query('.text');
|
|
65
|
+
expect(col.length).toBe(3);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('uses context parameter', () => {
|
|
69
|
+
const col = query('.text', '#main');
|
|
70
|
+
expect(col.length).toBe(3);
|
|
71
|
+
expect(col.first().textContent).toBe('Hello');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('supports chaining on result', () => {
|
|
75
|
+
const col = query('#main').addClass('chained');
|
|
76
|
+
expect(col.first().classList.contains('chained')).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// queryAll() — collection selector
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
describe('queryAll()', () => {
|
|
86
|
+
it('returns ZQueryCollection for CSS selector', () => {
|
|
87
|
+
const col = queryAll('.text');
|
|
88
|
+
expect(col).toBeInstanceOf(ZQueryCollection);
|
|
89
|
+
expect(col.length).toBe(3);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('returns empty collection for non-matching', () => {
|
|
93
|
+
const col = queryAll('.nonexistent');
|
|
94
|
+
expect(col.length).toBe(0);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('wraps single element', () => {
|
|
98
|
+
const div = document.createElement('div');
|
|
99
|
+
const col = queryAll(div);
|
|
100
|
+
expect(col.length).toBe(1);
|
|
101
|
+
expect(col.first()).toBe(div);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('creates elements from HTML', () => {
|
|
105
|
+
const col = queryAll('<li>a</li><li>b</li>');
|
|
106
|
+
expect(col.length).toBe(2);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('returns empty for null', () => {
|
|
110
|
+
expect(queryAll(null).length).toBe(0);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// ZQueryCollection methods
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
describe('ZQueryCollection', () => {
|
|
120
|
+
describe('iteration', () => {
|
|
121
|
+
it('each() iterates elements', () => {
|
|
122
|
+
const col = queryAll('.text');
|
|
123
|
+
const tags = [];
|
|
124
|
+
col.each((_, el) => tags.push(el.textContent));
|
|
125
|
+
expect(tags).toEqual(['Hello', 'World', 'Extra']);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('map() maps elements', () => {
|
|
129
|
+
const texts = queryAll('.text').map((_, el) => el.textContent);
|
|
130
|
+
expect(texts).toEqual(['Hello', 'World', 'Extra']);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('first() and last()', () => {
|
|
134
|
+
const col = queryAll('.text');
|
|
135
|
+
expect(col.first().textContent).toBe('Hello');
|
|
136
|
+
expect(col.last().textContent).toBe('Extra');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('eq() returns sub-collection', () => {
|
|
140
|
+
const col = queryAll('.text');
|
|
141
|
+
expect(col.eq(1).first().textContent).toBe('World');
|
|
142
|
+
expect(col.eq(5).length).toBe(0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('toArray() returns plain array', () => {
|
|
146
|
+
const arr = queryAll('.text').toArray();
|
|
147
|
+
expect(Array.isArray(arr)).toBe(true);
|
|
148
|
+
expect(arr.length).toBe(3);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('is iterable', () => {
|
|
152
|
+
const col = queryAll('.text');
|
|
153
|
+
const items = [...col];
|
|
154
|
+
expect(items.length).toBe(3);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
describe('traversal', () => {
|
|
160
|
+
it('find() searches descendants', () => {
|
|
161
|
+
const main = queryAll('#main');
|
|
162
|
+
const ps = main.find('.text');
|
|
163
|
+
expect(ps.length).toBe(3);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('parent() returns parents', () => {
|
|
167
|
+
const p = queryAll('.text').parent();
|
|
168
|
+
expect(p.first().id).toBe('main');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('children() returns direct children', () => {
|
|
172
|
+
const main = queryAll('#main');
|
|
173
|
+
expect(main.children().length).toBe(4); // 3 p + 1 span
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('filter() with string selector', () => {
|
|
177
|
+
const col = queryAll('#main').children();
|
|
178
|
+
const ps = col.filter('p');
|
|
179
|
+
expect(ps.length).toBe(3);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('filter() with function', () => {
|
|
183
|
+
const col = queryAll('#main').children();
|
|
184
|
+
const ps = col.filter(el => el.tagName === 'P');
|
|
185
|
+
expect(ps.length).toBe(3);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('not() excludes elements', () => {
|
|
189
|
+
const col = queryAll('#main').children();
|
|
190
|
+
const nonP = col.not('p');
|
|
191
|
+
expect(nonP.length).toBe(1);
|
|
192
|
+
expect(nonP.first().tagName).toBe('SPAN');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('next() returns next sibling', () => {
|
|
196
|
+
const col = queryAll('.first-p');
|
|
197
|
+
expect(col.next().first().classList.contains('second-p')).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('next(selector) filters by selector', () => {
|
|
201
|
+
const col = queryAll('.first-p');
|
|
202
|
+
expect(col.next('.second-p').length).toBe(1);
|
|
203
|
+
expect(col.next('.third-p').length).toBe(0);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('prev() returns previous sibling', () => {
|
|
207
|
+
const col = queryAll('.second-p');
|
|
208
|
+
expect(col.prev().first().classList.contains('first-p')).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('prev(selector) filters by selector', () => {
|
|
212
|
+
const col = queryAll('.second-p');
|
|
213
|
+
expect(col.prev('.first-p').length).toBe(1);
|
|
214
|
+
expect(col.prev('.other').length).toBe(0);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('nextAll() returns all following siblings', () => {
|
|
218
|
+
const col = queryAll('.first-p');
|
|
219
|
+
expect(col.nextAll().length).toBe(3); // second-p, other, third-p
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('nextAll(selector) filters', () => {
|
|
223
|
+
const col = queryAll('.first-p');
|
|
224
|
+
expect(col.nextAll('.text').length).toBe(2);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('nextUntil() stops at selector', () => {
|
|
228
|
+
const col = queryAll('.first-p');
|
|
229
|
+
const result = col.nextUntil('.third-p');
|
|
230
|
+
expect(result.length).toBe(2); // second-p, other
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('prevAll() returns all preceding siblings', () => {
|
|
234
|
+
const col = queryAll('.third-p');
|
|
235
|
+
expect(col.prevAll().length).toBe(3);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('prevAll(selector) filters', () => {
|
|
239
|
+
const col = queryAll('.third-p');
|
|
240
|
+
expect(col.prevAll('.text').length).toBe(2);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('prevUntil() stops at selector', () => {
|
|
244
|
+
const col = queryAll('.third-p');
|
|
245
|
+
const result = col.prevUntil('.first-p');
|
|
246
|
+
expect(result.length).toBe(2); // other, second-p
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('parents() returns all ancestors', () => {
|
|
250
|
+
const col = queryAll('.first-p');
|
|
251
|
+
const parents = col.parents();
|
|
252
|
+
expect(parents.length).toBeGreaterThanOrEqual(2); // #main, body, html
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('parents(selector) filters', () => {
|
|
256
|
+
const col = queryAll('.first-p');
|
|
257
|
+
expect(col.parents('#main').length).toBe(1);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('parentsUntil() stops at selector', () => {
|
|
261
|
+
const col = queryAll('.first-p');
|
|
262
|
+
const result = col.parentsUntil('body');
|
|
263
|
+
expect(result.length).toBe(1); // just #main
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('contents() includes text nodes', () => {
|
|
267
|
+
document.body.innerHTML = '<div id="ct">text<span>child</span></div>';
|
|
268
|
+
const col = queryAll('#ct');
|
|
269
|
+
expect(col.contents().length).toBe(2); // text node + span
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('siblings() returns all siblings excluding self', () => {
|
|
273
|
+
const col = queryAll('.other');
|
|
274
|
+
const sibs = col.siblings();
|
|
275
|
+
expect(sibs.length).toBe(3); // three .text p's
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('closest() finds ancestor', () => {
|
|
279
|
+
const col = queryAll('.nav-item').eq(0);
|
|
280
|
+
expect(col.closest('#nav').length).toBe(1);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
describe('classes', () => {
|
|
286
|
+
it('addClass / hasClass / removeClass', () => {
|
|
287
|
+
const col = queryAll('#main');
|
|
288
|
+
col.addClass('active');
|
|
289
|
+
expect(col.hasClass('active')).toBe(true);
|
|
290
|
+
col.removeClass('active');
|
|
291
|
+
expect(col.hasClass('active')).toBe(false);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('toggleClass', () => {
|
|
295
|
+
const col = queryAll('#main');
|
|
296
|
+
col.toggleClass('toggled');
|
|
297
|
+
expect(col.hasClass('toggled')).toBe(true);
|
|
298
|
+
col.toggleClass('toggled');
|
|
299
|
+
expect(col.hasClass('toggled')).toBe(false);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('toggleClass with multiple classes', () => {
|
|
303
|
+
const col = queryAll('#main');
|
|
304
|
+
col.toggleClass('a', 'b');
|
|
305
|
+
expect(col[0].classList.contains('a')).toBe(true);
|
|
306
|
+
expect(col[0].classList.contains('b')).toBe(true);
|
|
307
|
+
col.toggleClass('a', 'b');
|
|
308
|
+
expect(col[0].classList.contains('a')).toBe(false);
|
|
309
|
+
expect(col[0].classList.contains('b')).toBe(false);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('toggleClass with force boolean', () => {
|
|
313
|
+
const col = queryAll('#main');
|
|
314
|
+
col.toggleClass('forced', true);
|
|
315
|
+
expect(col.hasClass('forced')).toBe(true);
|
|
316
|
+
col.toggleClass('forced', true);
|
|
317
|
+
expect(col.hasClass('forced')).toBe(true);
|
|
318
|
+
col.toggleClass('forced', false);
|
|
319
|
+
expect(col.hasClass('forced')).toBe(false);
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
describe('attributes', () => {
|
|
325
|
+
it('attr get/set', () => {
|
|
326
|
+
const col = queryAll('#main');
|
|
327
|
+
col.attr('data-test', 'value');
|
|
328
|
+
expect(col.attr('data-test')).toBe('value');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('removeAttr', () => {
|
|
332
|
+
const col = queryAll('#main');
|
|
333
|
+
col.attr('data-x', 'y');
|
|
334
|
+
col.removeAttr('data-x');
|
|
335
|
+
expect(col.attr('data-x')).toBeNull();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('data get/set', () => {
|
|
339
|
+
const col = queryAll('#main');
|
|
340
|
+
col.data('count', 42);
|
|
341
|
+
expect(col.data('count')).toBe(42);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('data handles objects via JSON', () => {
|
|
345
|
+
const col = queryAll('#main');
|
|
346
|
+
col.data('info', { a: 1 });
|
|
347
|
+
expect(col.data('info')).toEqual({ a: 1 });
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
describe('content', () => {
|
|
353
|
+
it('html get/set', () => {
|
|
354
|
+
const col = queryAll('#main');
|
|
355
|
+
col.html('<b>bold</b>');
|
|
356
|
+
expect(col.html()).toBe('<b>bold</b>');
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('text get/set', () => {
|
|
360
|
+
const col = queryAll('.text').eq(0);
|
|
361
|
+
col.text('Changed');
|
|
362
|
+
expect(col.text()).toBe('Changed');
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
describe('DOM manipulation', () => {
|
|
368
|
+
it('append() adds content', () => {
|
|
369
|
+
const col = queryAll('#main');
|
|
370
|
+
col.append('<div class="appended">new</div>');
|
|
371
|
+
expect(document.querySelector('.appended').textContent).toBe('new');
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('prepend() adds at start', () => {
|
|
375
|
+
const col = queryAll('#main');
|
|
376
|
+
col.prepend('<div class="first">start</div>');
|
|
377
|
+
expect(col.first().firstElementChild.className).toBe('first');
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('remove() removes elements', () => {
|
|
381
|
+
queryAll('.other').remove();
|
|
382
|
+
expect(document.querySelector('.other')).toBeNull();
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('empty() clears content', () => {
|
|
386
|
+
queryAll('#main').empty();
|
|
387
|
+
expect(document.querySelector('#main').children.length).toBe(0);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('clone() creates deep copy', () => {
|
|
391
|
+
const col = queryAll('.text').eq(0);
|
|
392
|
+
const clone = col.clone();
|
|
393
|
+
expect(clone.first().textContent).toBe('Hello');
|
|
394
|
+
expect(clone.first()).not.toBe(col.first());
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
describe('visibility', () => {
|
|
400
|
+
it('hide() sets display none', () => {
|
|
401
|
+
queryAll('#main').hide();
|
|
402
|
+
expect(document.getElementById('main').style.display).toBe('none');
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('show() clears display', () => {
|
|
406
|
+
const main = document.getElementById('main');
|
|
407
|
+
main.style.display = 'none';
|
|
408
|
+
queryAll('#main').show();
|
|
409
|
+
expect(main.style.display).toBe('');
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
describe('events', () => {
|
|
415
|
+
it('on() and trigger()', () => {
|
|
416
|
+
let clicked = false;
|
|
417
|
+
const col = queryAll('#main');
|
|
418
|
+
col.on('click', () => { clicked = true; });
|
|
419
|
+
col.trigger('click');
|
|
420
|
+
expect(clicked).toBe(true);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('off() removes handler', () => {
|
|
424
|
+
let count = 0;
|
|
425
|
+
const handler = () => { count++; };
|
|
426
|
+
const col = queryAll('#main');
|
|
427
|
+
col.on('click', handler);
|
|
428
|
+
col.trigger('click');
|
|
429
|
+
col.off('click', handler);
|
|
430
|
+
col.trigger('click');
|
|
431
|
+
expect(count).toBe(1);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('on() works on non-Element targets like window', () => {
|
|
435
|
+
let fired = false;
|
|
436
|
+
const col = new ZQueryCollection([window]);
|
|
437
|
+
col.on('custom-win-evt', () => { fired = true; });
|
|
438
|
+
window.dispatchEvent(new Event('custom-win-evt'));
|
|
439
|
+
expect(fired).toBe(true);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('$.on() with EventTarget binds directly to that target', () => {
|
|
443
|
+
let fired = false;
|
|
444
|
+
const target = new EventTarget();
|
|
445
|
+
query.on('test-evt', target, () => { fired = true; });
|
|
446
|
+
target.dispatchEvent(new Event('test-evt'));
|
|
447
|
+
expect(fired).toBe(true);
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
// ---------------------------------------------------------------------------
|
|
454
|
+
// Quick ref helpers
|
|
455
|
+
// ---------------------------------------------------------------------------
|
|
456
|
+
|
|
457
|
+
describe('query quick refs', () => {
|
|
458
|
+
it('$.id() returns element by id', () => {
|
|
459
|
+
expect(query.id('main')).toBe(document.getElementById('main'));
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it('$.class() returns first element by class', () => {
|
|
463
|
+
expect(query.class('text').textContent).toBe('Hello');
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('$.classes() returns ZQueryCollection', () => {
|
|
467
|
+
const col = query.classes('text');
|
|
468
|
+
expect(col).toBeInstanceOf(ZQueryCollection);
|
|
469
|
+
expect(col.length).toBe(3);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('$.children() returns ZQueryCollection', () => {
|
|
473
|
+
const col = query.children('main');
|
|
474
|
+
expect(col).toBeInstanceOf(ZQueryCollection);
|
|
475
|
+
expect(col.length).toBe(4);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('$.tag() returns ZQueryCollection', () => {
|
|
479
|
+
const col = query.tag('p');
|
|
480
|
+
expect(col).toBeInstanceOf(ZQueryCollection);
|
|
481
|
+
expect(col.length).toBe(3);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
describe('collection forEach() works like Array.forEach()', () => {
|
|
485
|
+
it('iterates with el, index, array args', () => {
|
|
486
|
+
const results = [];
|
|
487
|
+
query.classes('text').forEach((el, i) => results.push({ i, text: el.textContent }));
|
|
488
|
+
expect(results).toEqual([{ i: 0, text: 'Hello' }, { i: 1, text: 'World' }, { i: 2, text: 'Extra' }]);
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('$.create() creates element with attributes', () => {
|
|
493
|
+
const col = query.create('div', { class: 'new', id: 'created' }, 'text');
|
|
494
|
+
expect(col).toBeInstanceOf(ZQueryCollection);
|
|
495
|
+
expect(col.length).toBe(1);
|
|
496
|
+
const el = col[0];
|
|
497
|
+
expect(el.tagName).toBe('DIV');
|
|
498
|
+
expect(el.className).toBe('new');
|
|
499
|
+
expect(el.id).toBe('created');
|
|
500
|
+
expect(el.textContent).toBe('text');
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
// ---------------------------------------------------------------------------
|
|
506
|
+
// New filtering / collection methods
|
|
507
|
+
// ---------------------------------------------------------------------------
|
|
508
|
+
|
|
509
|
+
describe('filtering & collection', () => {
|
|
510
|
+
it('is() checks if any element matches selector', () => {
|
|
511
|
+
expect(queryAll('#main').children().is('p')).toBe(true);
|
|
512
|
+
expect(queryAll('#main').children().is('table')).toBe(false);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('is() with function', () => {
|
|
516
|
+
const result = queryAll('.text').is(function(i) { return this.textContent === 'World'; });
|
|
517
|
+
expect(result).toBe(true);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('has() keeps elements containing matching descendant', () => {
|
|
521
|
+
const col = queryAll('#sidebar').has('.nav-item');
|
|
522
|
+
expect(col.length).toBe(1);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('slice() returns subset', () => {
|
|
526
|
+
const col = queryAll('.text').slice(0, 2);
|
|
527
|
+
expect(col.length).toBe(2);
|
|
528
|
+
expect(col.first().textContent).toBe('Hello');
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('slice() with negative index', () => {
|
|
532
|
+
const col = queryAll('.text').slice(-1);
|
|
533
|
+
expect(col.length).toBe(1);
|
|
534
|
+
expect(col.first().textContent).toBe('Extra');
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('add() merges collections', () => {
|
|
538
|
+
const col = queryAll('.first-p').add('.other');
|
|
539
|
+
expect(col.length).toBe(2);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it('add() with element', () => {
|
|
543
|
+
const el = document.getElementById('main');
|
|
544
|
+
const col = queryAll('.first-p').add(el);
|
|
545
|
+
expect(col.length).toBe(2);
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it('add() with ZQueryCollection', () => {
|
|
549
|
+
const col = queryAll('.first-p').add(queryAll('.other'));
|
|
550
|
+
expect(col.length).toBe(2);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it('get() with no args returns array', () => {
|
|
554
|
+
const arr = queryAll('.text').get();
|
|
555
|
+
expect(Array.isArray(arr)).toBe(true);
|
|
556
|
+
expect(arr.length).toBe(3);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it('get(index) returns element', () => {
|
|
560
|
+
const el = queryAll('.text').get(1);
|
|
561
|
+
expect(el.textContent).toBe('World');
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it('get(negative) returns from end', () => {
|
|
565
|
+
const el = queryAll('.text').get(-1);
|
|
566
|
+
expect(el.textContent).toBe('Extra');
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
it('index() returns position among siblings', () => {
|
|
570
|
+
const col = queryAll('.other');
|
|
571
|
+
expect(col.index()).toBe(2); // 3rd child (0-indexed)
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it('index(element) returns position in collection', () => {
|
|
575
|
+
const other = document.querySelector('.other');
|
|
576
|
+
const col = queryAll('#main').children();
|
|
577
|
+
expect(col.index(other)).toBe(2);
|
|
578
|
+
});
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
// ---------------------------------------------------------------------------
|
|
583
|
+
// Inverse DOM manipulation (appendTo, prependTo, insertAfter, insertBefore, etc.)
|
|
584
|
+
// ---------------------------------------------------------------------------
|
|
585
|
+
|
|
586
|
+
describe('inverse DOM manipulation', () => {
|
|
587
|
+
it('appendTo() moves elements into target', () => {
|
|
588
|
+
queryAll('<div class="new-item">new</div>').appendTo('#nav');
|
|
589
|
+
const nav = document.getElementById('nav');
|
|
590
|
+
expect(nav.lastElementChild.className).toBe('new-item');
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
it('prependTo() inserts at start of target', () => {
|
|
594
|
+
queryAll('<li class="first-item">first</li>').prependTo('#nav');
|
|
595
|
+
const nav = document.getElementById('nav');
|
|
596
|
+
expect(nav.firstElementChild.className).toBe('first-item');
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it('insertAfter() inserts after target', () => {
|
|
600
|
+
queryAll('<span class="inserted">!</span>').insertAfter('.first-p');
|
|
601
|
+
const firstP = document.querySelector('.first-p');
|
|
602
|
+
expect(firstP.nextElementSibling.className).toBe('inserted');
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it('insertBefore() inserts before target', () => {
|
|
606
|
+
queryAll('<span class="inserted">!</span>').insertBefore('.second-p');
|
|
607
|
+
const secondP = document.querySelector('.second-p');
|
|
608
|
+
expect(secondP.previousElementSibling.className).toBe('inserted');
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
it('replaceAll() replaces target elements', () => {
|
|
612
|
+
queryAll('<em>replaced</em>').replaceAll('.other');
|
|
613
|
+
expect(document.querySelector('.other')).toBeNull();
|
|
614
|
+
expect(document.querySelector('em').textContent).toBe('replaced');
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it('unwrap() removes parent wrapper', () => {
|
|
618
|
+
// wrap nav-items in a div first
|
|
619
|
+
const items = queryAll('.nav-item');
|
|
620
|
+
const count = items.length;
|
|
621
|
+
items.eq(0).wrap('<div class="wrapper"></div>');
|
|
622
|
+
expect(document.querySelector('.wrapper')).not.toBeNull();
|
|
623
|
+
queryAll('.nav-item').eq(0).unwrap('.wrapper');
|
|
624
|
+
expect(document.querySelector('.wrapper')).toBeNull();
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it('wrapAll() wraps all elements in one wrapper', () => {
|
|
628
|
+
queryAll('.nav-item').wrapAll('<div class="all-wrap"></div>');
|
|
629
|
+
const wrap = document.querySelector('.all-wrap');
|
|
630
|
+
expect(wrap).not.toBeNull();
|
|
631
|
+
expect(wrap.children.length).toBe(3);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
it('wrapInner() wraps inner contents', () => {
|
|
635
|
+
queryAll('.first-p').wrapInner('<strong></strong>');
|
|
636
|
+
const strong = document.querySelector('.first-p > strong');
|
|
637
|
+
expect(strong).not.toBeNull();
|
|
638
|
+
expect(strong.textContent).toBe('Hello');
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
it('detach() removes elements (alias for remove)', () => {
|
|
642
|
+
queryAll('.other').detach();
|
|
643
|
+
expect(document.querySelector('.other')).toBeNull();
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
// ---------------------------------------------------------------------------
|
|
649
|
+
// CSS dimension methods
|
|
650
|
+
// ---------------------------------------------------------------------------
|
|
651
|
+
|
|
652
|
+
describe('CSS dimension methods', () => {
|
|
653
|
+
it('scrollTop() get returns a number', () => {
|
|
654
|
+
const val = queryAll('#main').scrollTop();
|
|
655
|
+
expect(typeof val).toBe('number');
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
it('scrollTop(value) sets scroll position', () => {
|
|
659
|
+
const main = document.getElementById('main');
|
|
660
|
+
main.style.overflow = 'auto';
|
|
661
|
+
main.style.height = '10px';
|
|
662
|
+
main.innerHTML = '<div style="height:1000px">tall</div>';
|
|
663
|
+
queryAll('#main').scrollTop(50);
|
|
664
|
+
expect(main.scrollTop).toBe(50);
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
it('scrollLeft() get returns a number', () => {
|
|
668
|
+
const val = queryAll('#main').scrollLeft();
|
|
669
|
+
expect(typeof val).toBe('number');
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it('innerWidth() returns clientWidth', () => {
|
|
673
|
+
const main = document.getElementById('main');
|
|
674
|
+
// jsdom sets clientWidth to 0 but the method should still return a number
|
|
675
|
+
const val = queryAll('#main').innerWidth();
|
|
676
|
+
expect(typeof val).toBe('number');
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
it('innerHeight() returns clientHeight', () => {
|
|
680
|
+
const val = queryAll('#main').innerHeight();
|
|
681
|
+
expect(typeof val).toBe('number');
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
it('outerWidth() returns offsetWidth', () => {
|
|
685
|
+
const val = queryAll('#main').outerWidth();
|
|
686
|
+
expect(typeof val).toBe('number');
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
it('outerHeight() returns offsetHeight', () => {
|
|
690
|
+
const val = queryAll('#main').outerHeight();
|
|
691
|
+
expect(typeof val).toBe('number');
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
it('outerWidth(true) includes margin', () => {
|
|
695
|
+
const main = document.getElementById('main');
|
|
696
|
+
main.style.margin = '10px';
|
|
697
|
+
const val = queryAll('#main').outerWidth(true);
|
|
698
|
+
expect(typeof val).toBe('number');
|
|
699
|
+
});
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
// ---------------------------------------------------------------------------
|
|
704
|
+
// hover() convenience
|
|
705
|
+
// ---------------------------------------------------------------------------
|
|
706
|
+
|
|
707
|
+
describe('hover()', () => {
|
|
708
|
+
it('binds mouseenter and mouseleave', () => {
|
|
709
|
+
let entered = false, left = false;
|
|
710
|
+
const col = queryAll('#main');
|
|
711
|
+
col.hover(() => { entered = true; }, () => { left = true; });
|
|
712
|
+
col.first().dispatchEvent(new Event('mouseenter'));
|
|
713
|
+
col.first().dispatchEvent(new Event('mouseleave'));
|
|
714
|
+
expect(entered).toBe(true);
|
|
715
|
+
expect(left).toBe(true);
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
it('uses same fn for both if only one provided', () => {
|
|
719
|
+
let count = 0;
|
|
720
|
+
const col = queryAll('#main');
|
|
721
|
+
col.hover(() => { count++; });
|
|
722
|
+
col.first().dispatchEvent(new Event('mouseenter'));
|
|
723
|
+
col.first().dispatchEvent(new Event('mouseleave'));
|
|
724
|
+
expect(count).toBe(2);
|
|
725
|
+
});
|
|
726
|
+
});
|