create-gardener 2.0.6 → 2.0.8

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.
@@ -41,12 +41,12 @@ async function findTemplate(fileName: string) {
41
41
 
42
42
  export async function addPage(req: Request, res: Response) {
43
43
  try {
44
- const pagename: string = req.body.page;
45
- const name = pagename.replaceAll('/', '_');
44
+ const name: string = req.body.page;
45
+ // const name = pagename.replaceAll('/', '_');
46
46
 
47
47
 
48
48
 
49
- const templatePath = await findTemplate(name);//path.join(frontendDir, findTemplate(name)); //path.join(frontendDir, 'frontendtemplate.ejs');
49
+ const templatePath = await findTemplate(name + '.ejs');//path.join(frontendDir, findTemplate(name)); //path.join(frontendDir, 'frontendtemplate.ejs');
50
50
 
51
51
 
52
52
 
@@ -21,4 +21,4 @@ if (process.env.NODE_ENV !== 'production') {
21
21
 
22
22
 
23
23
 
24
- router.route('/').get((req: Request, res: Response) => res.render('_'));
24
+ router.route('/').get((req: Request, res: Response) => res.render('_', { fileName: '_' }));
@@ -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
+ });
@@ -78,7 +78,7 @@ const pagebtns = gardener({
78
78
  const result = await fetch('/savetemplate', {
79
79
  method: 'POST',
80
80
  headers: { "Content-Type": 'application/json' },
81
- body: JSON.stringify({ path: window.location.pathname })
81
+ body: JSON.stringify({ path: fetchElement('#fileName').innerText })
82
82
  });
83
83
 
84
84
  const data = await result.json(); // ✅ fix
@@ -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) });
@@ -273,9 +273,6 @@
273
273
  .bottom-4 {
274
274
  bottom: calc(var(--spacing) * 4);
275
275
  }
276
- .bottom-6 {
277
- bottom: calc(var(--spacing) * 6);
278
- }
279
276
  .bottom-20 {
280
277
  bottom: calc(var(--spacing) * 20);
281
278
  }
@@ -306,6 +303,24 @@
306
303
  .z-100 {
307
304
  z-index: 100;
308
305
  }
306
+ .container {
307
+ width: 100%;
308
+ @media (width >= 40rem) {
309
+ max-width: 40rem;
310
+ }
311
+ @media (width >= 48rem) {
312
+ max-width: 48rem;
313
+ }
314
+ @media (width >= 64rem) {
315
+ max-width: 64rem;
316
+ }
317
+ @media (width >= 80rem) {
318
+ max-width: 80rem;
319
+ }
320
+ @media (width >= 96rem) {
321
+ max-width: 96rem;
322
+ }
323
+ }
309
324
  .m-2 {
310
325
  margin: calc(var(--spacing) * 2);
311
326
  }
@@ -351,6 +366,9 @@
351
366
  .grid {
352
367
  display: grid;
353
368
  }
369
+ .hidden {
370
+ display: none;
371
+ }
354
372
  .inline {
355
373
  display: inline;
356
374
  }
@@ -369,9 +387,6 @@
369
387
  .h-15 {
370
388
  height: calc(var(--spacing) * 15);
371
389
  }
372
- .h-35 {
373
- height: calc(var(--spacing) * 35);
374
- }
375
390
  .h-96 {
376
391
  height: calc(var(--spacing) * 96);
377
392
  }
@@ -393,12 +408,6 @@
393
408
  .w-15 {
394
409
  width: calc(var(--spacing) * 15);
395
410
  }
396
- .w-35 {
397
- width: calc(var(--spacing) * 35);
398
- }
399
- .w-50 {
400
- width: calc(var(--spacing) * 50);
401
- }
402
411
  .w-96 {
403
412
  width: calc(var(--spacing) * 96);
404
413
  }
@@ -555,9 +564,6 @@
555
564
  .bg-gray-200 {
556
565
  background-color: var(--color-gray-200);
557
566
  }
558
- .bg-gray-400 {
559
- background-color: var(--color-gray-400);
560
- }
561
567
  .bg-gray-500 {
562
568
  background-color: var(--color-gray-500);
563
569
  }
@@ -570,9 +576,6 @@
570
576
  .bg-green-300 {
571
577
  background-color: var(--color-green-300);
572
578
  }
573
- .bg-green-500 {
574
- background-color: var(--color-green-500);
575
- }
576
579
  .bg-green-700 {
577
580
  background-color: var(--color-green-700);
578
581
  }
@@ -793,13 +796,6 @@
793
796
  }
794
797
  }
795
798
  }
796
- .hover\:bg-gray-800 {
797
- &:hover {
798
- @media (hover: hover) {
799
- background-color: var(--color-gray-800);
800
- }
801
- }
802
- }
803
799
  .hover\:bg-gray-900 {
804
800
  &:hover {
805
801
  @media (hover: hover) {
@@ -807,13 +803,6 @@
807
803
  }
808
804
  }
809
805
  }
810
- .hover\:bg-green-600 {
811
- &:hover {
812
- @media (hover: hover) {
813
- background-color: var(--color-green-600);
814
- }
815
- }
816
- }
817
806
  .hover\:bg-green-800 {
818
807
  &:hover {
819
808
  @media (hover: hover) {
@@ -10,6 +10,7 @@
10
10
  </head>
11
11
  <body class="bg-slate-50 text-slate-900 font-sans">
12
12
 
13
+ <div class='hidden' id='fileName'><%=fileName%></div>
13
14
  <div class='loader w-screen h-screen bg-white fixed z-2'> </div>
14
15
  <div class='notification'></div>
15
16
 
@@ -10,6 +10,7 @@
10
10
  </head>
11
11
  <body class="bg-slate-50 text-slate-900 font-sans">
12
12
 
13
+ <div class='hidden' id='fileName'><%=fileName%></div>
13
14
  <div class='loader w-screen h-screen bg-white fixed z-2'> </div>
14
15
  <div class='notification'></div>
15
16
 
File without changes