create-gardener 2.0.6 → 2.0.7
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 +187 -0
- package/package.json +1 -1
- package/template/buildHelper.js +5 -4
- package/template/buildHelper.test.js +78 -0
- package/template/jest.config.js +13 -0
- package/template/package.json +4 -1
- package/template/pnpm-lock.yaml +2761 -17
- package/template/src/frontend/static/gardener.test.js +364 -0
- package/template/src/frontend/static/gardenerDev.js +3 -6
- /package/{Readme.md → Readme.deprecated.md} +0 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, test, expect, beforeEach, jest } from '@jest/globals';
|
|
6
|
+
import {
|
|
7
|
+
fetchElement,
|
|
8
|
+
appendElement,
|
|
9
|
+
createElement,
|
|
10
|
+
insertText,
|
|
11
|
+
replaceElement,
|
|
12
|
+
gardener
|
|
13
|
+
} from './gardener.js';
|
|
14
|
+
|
|
15
|
+
describe('fetchElement', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
document.body.innerHTML = '';
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('should fetch element by query selector', () => {
|
|
21
|
+
document.body.innerHTML = '<div class="test-class"></div>';
|
|
22
|
+
const element = fetchElement('.test-class');
|
|
23
|
+
expect(element).toBeTruthy();
|
|
24
|
+
expect(element.className).toBe('test-class');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('should return null for non-existent element', () => {
|
|
28
|
+
const element = fetchElement('.non-existent');
|
|
29
|
+
expect(element).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('should fetch element by id', () => {
|
|
33
|
+
document.body.innerHTML = '<div id="test-id"></div>';
|
|
34
|
+
const element = fetchElement('#test-id');
|
|
35
|
+
expect(element).toBeTruthy();
|
|
36
|
+
expect(element.id).toBe('test-id');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('appendElement', () => {
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
document.body.innerHTML = '';
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('should append child to parent element', () => {
|
|
46
|
+
const parent = document.createElement('div');
|
|
47
|
+
const child = document.createElement('span');
|
|
48
|
+
document.body.appendChild(parent);
|
|
49
|
+
|
|
50
|
+
appendElement(parent, child);
|
|
51
|
+
expect(parent.children.length).toBe(1);
|
|
52
|
+
expect(parent.firstChild).toBe(child);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('should append child using parent selector string', () => {
|
|
56
|
+
document.body.innerHTML = '<div class="parent"></div>';
|
|
57
|
+
const child = document.createElement('span');
|
|
58
|
+
|
|
59
|
+
appendElement('.parent', child);
|
|
60
|
+
const parent = document.querySelector('.parent');
|
|
61
|
+
expect(parent.children.length).toBe(1);
|
|
62
|
+
expect(parent.firstChild).toBe(child);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('should append multiple children', () => {
|
|
66
|
+
const parent = document.createElement('div');
|
|
67
|
+
const child1 = document.createElement('span');
|
|
68
|
+
const child2 = document.createElement('p');
|
|
69
|
+
|
|
70
|
+
appendElement(parent, child1);
|
|
71
|
+
appendElement(parent, child2);
|
|
72
|
+
|
|
73
|
+
expect(parent.children.length).toBe(2);
|
|
74
|
+
expect(parent.children[0]).toBe(child1);
|
|
75
|
+
expect(parent.children[1]).toBe(child2);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('createElement', () => {
|
|
80
|
+
test('should create element with given type', () => {
|
|
81
|
+
const element = createElement('div');
|
|
82
|
+
expect(element.tagName).toBe('DIV');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('should create element with single class', () => {
|
|
86
|
+
const element = createElement('div', ['test-class']);
|
|
87
|
+
expect(element.classList.contains('test-class')).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('should create element with multiple classes', () => {
|
|
91
|
+
const element = createElement('span', ['class1', 'class2', 'class3']);
|
|
92
|
+
expect(element.classList.contains('class1')).toBe(true);
|
|
93
|
+
expect(element.classList.contains('class2')).toBe(true);
|
|
94
|
+
expect(element.classList.contains('class3')).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('should create element without class if not provided', () => {
|
|
98
|
+
const element = createElement('p');
|
|
99
|
+
expect(element.className).toBe('');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('insertText', () => {
|
|
104
|
+
test('should insert text into element', () => {
|
|
105
|
+
const element = document.createElement('div');
|
|
106
|
+
insertText(element, 'Hello World');
|
|
107
|
+
expect(element.innerText).toBe('Hello World');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('should replace existing text', () => {
|
|
111
|
+
const element = document.createElement('div');
|
|
112
|
+
element.innerText = 'Old Text';
|
|
113
|
+
insertText(element, 'New Text');
|
|
114
|
+
expect(element.innerText).toBe('New Text');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('should handle empty string', () => {
|
|
118
|
+
const element = document.createElement('div');
|
|
119
|
+
insertText(element, '');
|
|
120
|
+
expect(element.innerText).toBe('');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('replaceElement', () => {
|
|
125
|
+
beforeEach(() => {
|
|
126
|
+
document.body.innerHTML = '';
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('should replace element with another element', () => {
|
|
130
|
+
const original = document.createElement('div');
|
|
131
|
+
original.id = 'original';
|
|
132
|
+
const replacement = document.createElement('span');
|
|
133
|
+
replacement.id = 'replacement';
|
|
134
|
+
document.body.appendChild(original);
|
|
135
|
+
|
|
136
|
+
replaceElement(original, replacement);
|
|
137
|
+
expect(document.getElementById('original')).toBeNull();
|
|
138
|
+
expect(document.getElementById('replacement')).toBeTruthy();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('should replace element using selector string', () => {
|
|
142
|
+
document.body.innerHTML = '<div class="original"></div>';
|
|
143
|
+
const replacement = document.createElement('span');
|
|
144
|
+
replacement.className = 'replacement';
|
|
145
|
+
|
|
146
|
+
replaceElement('.original', replacement);
|
|
147
|
+
expect(document.querySelector('.original')).toBeNull();
|
|
148
|
+
expect(document.querySelector('.replacement')).toBeTruthy();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('gardener', () => {
|
|
153
|
+
beforeEach(() => {
|
|
154
|
+
document.body.innerHTML = '';
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('should return DOM element if nodeType is 1', () => {
|
|
158
|
+
const element = document.createElement('div');
|
|
159
|
+
const result = gardener(element);
|
|
160
|
+
expect(result).toBe(element);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('should create simple element', () => {
|
|
164
|
+
const dom = { t: 'div' };
|
|
165
|
+
const element = gardener(dom);
|
|
166
|
+
expect(element.tagName).toBe('DIV');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('should create element with classes', () => {
|
|
170
|
+
const dom = { t: 'div', cn: ['class1', 'class2'] };
|
|
171
|
+
const element = gardener(dom);
|
|
172
|
+
expect(element.classList.contains('class1')).toBe(true);
|
|
173
|
+
expect(element.classList.contains('class2')).toBe(true);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('should create element with text content', () => {
|
|
177
|
+
const dom = { t: 'p', txt: 'Hello World' };
|
|
178
|
+
const element = gardener(dom);
|
|
179
|
+
expect(element.innerText).toBe('Hello World');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('should create element with attributes', () => {
|
|
183
|
+
const dom = {
|
|
184
|
+
t: 'input',
|
|
185
|
+
attr: { type: 'text', placeholder: 'Enter text', id: 'test-input' }
|
|
186
|
+
};
|
|
187
|
+
const element = gardener(dom);
|
|
188
|
+
expect(element.getAttribute('type')).toBe('text');
|
|
189
|
+
expect(element.getAttribute('placeholder')).toBe('Enter text');
|
|
190
|
+
expect(element.id).toBe('test-input');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('should create element with data attributes', () => {
|
|
194
|
+
const dom = {
|
|
195
|
+
t: 'div',
|
|
196
|
+
attr: { 'data-test': 'value', 'data-id': '123' }
|
|
197
|
+
};
|
|
198
|
+
const element = gardener(dom);
|
|
199
|
+
expect(element.getAttribute('data-test')).toBe('value');
|
|
200
|
+
expect(element.getAttribute('data-id')).toBe('123');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('should create element with aria attributes', () => {
|
|
204
|
+
const dom = {
|
|
205
|
+
t: 'button',
|
|
206
|
+
attr: { 'aria-label': 'Close', 'aria-expanded': 'false' }
|
|
207
|
+
};
|
|
208
|
+
const element = gardener(dom);
|
|
209
|
+
expect(element.getAttribute('aria-label')).toBe('Close');
|
|
210
|
+
expect(element.getAttribute('aria-expanded')).toBe('false');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test('should create element with event listeners', () => {
|
|
214
|
+
const mockHandler = jest.fn();
|
|
215
|
+
const dom = {
|
|
216
|
+
t: 'button',
|
|
217
|
+
events: { click: mockHandler }
|
|
218
|
+
};
|
|
219
|
+
const element = gardener(dom);
|
|
220
|
+
element.click();
|
|
221
|
+
expect(mockHandler).toHaveBeenCalledTimes(1);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('should create element with multiple event listeners', () => {
|
|
225
|
+
const clickHandler = jest.fn();
|
|
226
|
+
const mouseoverHandler = jest.fn();
|
|
227
|
+
const dom = {
|
|
228
|
+
t: 'div',
|
|
229
|
+
events: { click: clickHandler, mouseover: mouseoverHandler }
|
|
230
|
+
};
|
|
231
|
+
const element = gardener(dom);
|
|
232
|
+
|
|
233
|
+
element.click();
|
|
234
|
+
element.dispatchEvent(new Event('mouseover'));
|
|
235
|
+
|
|
236
|
+
expect(clickHandler).toHaveBeenCalledTimes(1);
|
|
237
|
+
expect(mouseoverHandler).toHaveBeenCalledTimes(1);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('should create nested elements', () => {
|
|
241
|
+
const dom = {
|
|
242
|
+
t: 'div',
|
|
243
|
+
children: [
|
|
244
|
+
{ t: 'span', txt: 'Child 1' },
|
|
245
|
+
{ t: 'p', txt: 'Child 2' }
|
|
246
|
+
]
|
|
247
|
+
};
|
|
248
|
+
const element = gardener(dom);
|
|
249
|
+
expect(element.children.length).toBe(2);
|
|
250
|
+
expect(element.children[0].tagName).toBe('SPAN');
|
|
251
|
+
expect(element.children[0].innerText).toBe('Child 1');
|
|
252
|
+
expect(element.children[1].tagName).toBe('P');
|
|
253
|
+
expect(element.children[1].innerText).toBe('Child 2');
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('should create deeply nested elements', () => {
|
|
257
|
+
const dom = {
|
|
258
|
+
t: 'div',
|
|
259
|
+
children: [
|
|
260
|
+
{
|
|
261
|
+
t: 'section',
|
|
262
|
+
children: [
|
|
263
|
+
{ t: 'h1', txt: 'Title' },
|
|
264
|
+
{ t: 'p', txt: 'Content' }
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
]
|
|
268
|
+
};
|
|
269
|
+
const element = gardener(dom);
|
|
270
|
+
expect(element.children.length).toBe(1);
|
|
271
|
+
expect(element.children[0].tagName).toBe('SECTION');
|
|
272
|
+
expect(element.children[0].children.length).toBe(2);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test('should create SVG element', () => {
|
|
276
|
+
const dom = { t: 'svg' };
|
|
277
|
+
const element = gardener(dom);
|
|
278
|
+
expect(element.tagName).toBe('svg');
|
|
279
|
+
expect(element.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('should create SVG path element', () => {
|
|
283
|
+
const dom = {
|
|
284
|
+
t: 'path',
|
|
285
|
+
attr: { d: 'M10 10 H 90 V 90 H 10 Z' }
|
|
286
|
+
};
|
|
287
|
+
const element = gardener(dom);
|
|
288
|
+
expect(element.tagName).toBe('path');
|
|
289
|
+
expect(element.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
290
|
+
expect(element.getAttribute('d')).toBe('M10 10 H 90 V 90 H 10 Z');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test('should create SVG with classes', () => {
|
|
294
|
+
const dom = {
|
|
295
|
+
t: 'circle',
|
|
296
|
+
cn: ['svg-class'],
|
|
297
|
+
attr: { cx: '50', cy: '50', r: '40' }
|
|
298
|
+
};
|
|
299
|
+
const element = gardener(dom);
|
|
300
|
+
expect(element.classList.contains('svg-class')).toBe(true);
|
|
301
|
+
expect(element.getAttribute('cx')).toBe('50');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('should create complex SVG structure', () => {
|
|
305
|
+
const dom = {
|
|
306
|
+
t: 'svg',
|
|
307
|
+
attr: { width: '100', height: '100' },
|
|
308
|
+
children: [
|
|
309
|
+
{ t: 'circle', attr: { cx: '50', cy: '50', r: '40' } },
|
|
310
|
+
{ t: 'rect', attr: { x: '10', y: '10', width: '30', height: '30' } }
|
|
311
|
+
]
|
|
312
|
+
};
|
|
313
|
+
const element = gardener(dom);
|
|
314
|
+
expect(element.children.length).toBe(2);
|
|
315
|
+
expect(element.children[0].tagName).toBe('circle');
|
|
316
|
+
expect(element.children[1].tagName).toBe('rect');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test('should handle property attributes like value', () => {
|
|
320
|
+
const dom = {
|
|
321
|
+
t: 'input',
|
|
322
|
+
attr: { value: 'test-value', selectedIndex: '2' }
|
|
323
|
+
};
|
|
324
|
+
const element = gardener(dom);
|
|
325
|
+
expect(element.getAttribute('value')).toBe('test-value');
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test('should handle empty string attribute as boolean', () => {
|
|
329
|
+
const dom = {
|
|
330
|
+
t: 'button',
|
|
331
|
+
attr: { disabled: '' }
|
|
332
|
+
};
|
|
333
|
+
const element = gardener(dom);
|
|
334
|
+
expect(element.disabled).toBe(true);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test('should create element with all features combined', () => {
|
|
338
|
+
const clickHandler = jest.fn();
|
|
339
|
+
const dom = {
|
|
340
|
+
t: 'div',
|
|
341
|
+
cn: ['container', 'main'],
|
|
342
|
+
txt: 'Container Text',
|
|
343
|
+
attr: { id: 'main-container', 'data-test': 'value' },
|
|
344
|
+
events: { click: clickHandler },
|
|
345
|
+
children: [
|
|
346
|
+
{ t: 'h1', txt: 'Title', cn: ['title'] },
|
|
347
|
+
{ t: 'p', txt: 'Paragraph' }
|
|
348
|
+
]
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const element = gardener(dom);
|
|
352
|
+
|
|
353
|
+
expect(element.tagName).toBe('DIV');
|
|
354
|
+
expect(element.classList.contains('container')).toBe(true);
|
|
355
|
+
expect(element.classList.contains('main')).toBe(true);
|
|
356
|
+
expect(element.innerText).toContain('Container Text');
|
|
357
|
+
expect(element.id).toBe('main-container');
|
|
358
|
+
expect(element.getAttribute('data-test')).toBe('value');
|
|
359
|
+
expect(element.children.length).toBe(2);
|
|
360
|
+
|
|
361
|
+
element.click();
|
|
362
|
+
expect(clickHandler).toHaveBeenCalled();
|
|
363
|
+
});
|
|
364
|
+
});
|
|
@@ -310,7 +310,6 @@ async function addComponent(txt, path) {
|
|
|
310
310
|
if (!res.ok) console.error('wrong');
|
|
311
311
|
|
|
312
312
|
const data = await res.json()
|
|
313
|
-
console.log(data);
|
|
314
313
|
|
|
315
314
|
}
|
|
316
315
|
catch (err) {
|
|
@@ -328,7 +327,6 @@ function opnPagedialog(btn = true) {
|
|
|
328
327
|
e.preventDefault()
|
|
329
328
|
const data = new FormData(e.target);
|
|
330
329
|
const input = Object.fromEntries(data.entries());
|
|
331
|
-
console.log(input)
|
|
332
330
|
|
|
333
331
|
const response = await fetch('/addpage', {
|
|
334
332
|
method: 'POST',
|
|
@@ -337,7 +335,6 @@ function opnPagedialog(btn = true) {
|
|
|
337
335
|
},
|
|
338
336
|
body: JSON.stringify(input)
|
|
339
337
|
}).then(res => res.json())
|
|
340
|
-
console.log(response)
|
|
341
338
|
opnPagedialog(false)
|
|
342
339
|
window.location.href = `${input.page}`
|
|
343
340
|
}
|
|
@@ -353,12 +350,10 @@ function opnPagedialog(btn = true) {
|
|
|
353
350
|
|
|
354
351
|
})
|
|
355
352
|
|
|
356
|
-
console.log('test')
|
|
357
353
|
appendElement(body, dialog);
|
|
358
354
|
fetchElement('.pathinput').focus();
|
|
359
355
|
}
|
|
360
356
|
else {
|
|
361
|
-
console.log('removed')
|
|
362
357
|
fetchElement('.addpageform').remove();
|
|
363
358
|
}
|
|
364
359
|
}
|
|
@@ -416,7 +411,6 @@ export function parser(element, isParent = true) {
|
|
|
416
411
|
element = fetchElement(element);
|
|
417
412
|
}
|
|
418
413
|
|
|
419
|
-
console.log(element)
|
|
420
414
|
const obj = {
|
|
421
415
|
t: element.tagName.toLowerCase(),
|
|
422
416
|
};
|
|
@@ -489,6 +483,9 @@ export class State {
|
|
|
489
483
|
cb(this.value);
|
|
490
484
|
this.cb.push(cb);
|
|
491
485
|
}
|
|
486
|
+
unregisterCb(cb) {
|
|
487
|
+
this.cb = this.cb.filter(c => c !== cb);
|
|
488
|
+
}
|
|
492
489
|
setTo(val) {
|
|
493
490
|
this.value = val;
|
|
494
491
|
this.cb.forEach(cb => { cb(val) });
|
|
File without changes
|