lego-dom 1.3.4 → 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.
- package/CHANGELOG.md +72 -1
- package/main.js +48 -17
- package/main.min.js +2 -2
- package/package.json +1 -1
- package/parse-lego.js +2 -2
- package/vite-plugin.js +0 -14
- package/.github/workflows/deploy-docs.yml +0 -56
- package/.legodom +0 -87
- package/docs/.vitepress/config.js +0 -161
- package/docs/api/config.md +0 -95
- package/docs/api/define.md +0 -58
- package/docs/api/directives.md +0 -50
- package/docs/api/globals.md +0 -29
- package/docs/api/index.md +0 -30
- package/docs/api/lifecycle.md +0 -40
- package/docs/api/route.md +0 -37
- package/docs/api/vite-plugin.md +0 -58
- package/docs/contributing/01-welcome.md +0 -38
- package/docs/contributing/02-registry.md +0 -133
- package/docs/contributing/03-batcher.md +0 -110
- package/docs/contributing/04-reactivity.md +0 -87
- package/docs/contributing/05-caching.md +0 -59
- package/docs/contributing/06-init.md +0 -136
- package/docs/contributing/07-observer.md +0 -72
- package/docs/contributing/08-snap.md +0 -140
- package/docs/contributing/09-diffing.md +0 -69
- package/docs/contributing/10-studs.md +0 -78
- package/docs/contributing/11-scanner.md +0 -117
- package/docs/contributing/12-render.md +0 -138
- package/docs/contributing/13-directives.md +0 -243
- package/docs/contributing/14-events.md +0 -57
- package/docs/contributing/15-router.md +0 -57
- package/docs/contributing/16-state.md +0 -47
- package/docs/contributing/17-legodom.md +0 -48
- package/docs/contributing/index.md +0 -24
- package/docs/examples/form.md +0 -42
- package/docs/examples/index.md +0 -104
- package/docs/examples/routing.md +0 -409
- package/docs/examples/sfc-showcase.md +0 -34
- package/docs/examples/todo-app.md +0 -383
- package/docs/guide/cdn-usage.md +0 -354
- package/docs/guide/components.md +0 -418
- package/docs/guide/directives.md +0 -539
- package/docs/guide/directory-structure.md +0 -248
- package/docs/guide/faq.md +0 -210
- package/docs/guide/getting-started.md +0 -262
- package/docs/guide/index.md +0 -88
- package/docs/guide/lifecycle.md +0 -525
- package/docs/guide/quick-start.md +0 -49
- package/docs/guide/reactivity.md +0 -415
- package/docs/guide/routing.md +0 -334
- package/docs/guide/server-side.md +0 -134
- package/docs/guide/sfc.md +0 -464
- package/docs/guide/templating.md +0 -388
- package/docs/index.md +0 -160
- package/docs/public/logo.svg +0 -17
- package/docs/router/basic-routing.md +0 -103
- package/docs/router/cold-entry.md +0 -91
- package/docs/router/history.md +0 -69
- package/docs/router/index.md +0 -73
- package/docs/router/resolver.md +0 -74
- package/docs/router/surgical-swaps.md +0 -134
- package/docs/tutorial/01-project-setup.md +0 -152
- package/docs/tutorial/02-your-first-component.md +0 -226
- package/docs/tutorial/03-adding-routes.md +0 -279
- package/docs/tutorial/04-multi-page-app.md +0 -329
- package/docs/tutorial/05-state-and-globals.md +0 -285
- package/docs/tutorial/index.md +0 -40
- package/examples/vite-app/README.md +0 -71
- package/examples/vite-app/index.html +0 -42
- package/examples/vite-app/package.json +0 -18
- package/examples/vite-app/src/app.css +0 -3
- package/examples/vite-app/src/app.js +0 -29
- package/examples/vite-app/src/components/app-navbar.lego +0 -34
- package/examples/vite-app/src/components/customers/customer-details.lego +0 -24
- package/examples/vite-app/src/components/customers/customer-orders.lego +0 -21
- package/examples/vite-app/src/components/customers/order-list.lego +0 -55
- package/examples/vite-app/src/components/greeting-card.lego +0 -41
- package/examples/vite-app/src/components/sample-component.lego +0 -75
- package/examples/vite-app/src/components/shells/customers-shell.lego +0 -21
- package/examples/vite-app/src/components/side-menu.lego +0 -46
- package/examples/vite-app/src/components/todo-list.lego +0 -239
- package/examples/vite-app/src/components/widgets/user-card.lego +0 -27
- package/examples/vite-app/vite.config.js +0 -22
- package/tests/error.test.js +0 -74
- package/tests/main.test.js +0 -103
- package/tests/memory.test.js +0 -68
- package/tests/monitoring.test.js +0 -74
- package/tests/naming.test.js +0 -74
- package/tests/parse-lego.test.js +0 -65
- package/tests/security.test.js +0 -67
- package/tests/server.test.js +0 -114
- package/tests/syntax.test.js +0 -67
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
<style>
|
|
2
|
-
self {
|
|
3
|
-
display: block;
|
|
4
|
-
background: white;
|
|
5
|
-
border-radius: 8px;
|
|
6
|
-
padding: 1.5rem;
|
|
7
|
-
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
.input-group {
|
|
11
|
-
display: flex;
|
|
12
|
-
gap: 0.5rem;
|
|
13
|
-
margin-bottom: 1.5rem;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
input[type="text"] {
|
|
17
|
-
flex: 1;
|
|
18
|
-
padding: 0.75rem;
|
|
19
|
-
font-size: 1rem;
|
|
20
|
-
border: 2px solid #e0e0e0;
|
|
21
|
-
border-radius: 4px;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
input[type="text"]:focus {
|
|
25
|
-
outline: none;
|
|
26
|
-
border-color: #4CAF50;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.btn {
|
|
30
|
-
padding: 0.75rem 1.5rem;
|
|
31
|
-
font-size: 1rem;
|
|
32
|
-
border: none;
|
|
33
|
-
border-radius: 4px;
|
|
34
|
-
cursor: pointer;
|
|
35
|
-
font-weight: 600;
|
|
36
|
-
transition: background 0.2s;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.btn-primary {
|
|
40
|
-
background: #4CAF50;
|
|
41
|
-
color: white;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.btn-primary:hover {
|
|
45
|
-
background: #45a049;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.filters {
|
|
49
|
-
display: flex;
|
|
50
|
-
gap: 0.5rem;
|
|
51
|
-
margin-bottom: 1rem;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.filter-btn {
|
|
55
|
-
padding: 0.5rem 1rem;
|
|
56
|
-
background: #f0f0f0;
|
|
57
|
-
border: none;
|
|
58
|
-
border-radius: 4px;
|
|
59
|
-
cursor: pointer;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.filter-btn.active {
|
|
63
|
-
background: #4CAF50;
|
|
64
|
-
color: white;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
ul {
|
|
68
|
-
list-style: none;
|
|
69
|
-
padding: 0;
|
|
70
|
-
margin: 0;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
li {
|
|
74
|
-
display: flex;
|
|
75
|
-
align-items: center;
|
|
76
|
-
gap: 0.75rem;
|
|
77
|
-
padding: 0.75rem;
|
|
78
|
-
border-bottom: 1px solid #f0f0f0;
|
|
79
|
-
transition: background 0.2s;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
li:hover {
|
|
83
|
-
background: #f9f9f9;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
input[type="checkbox"] {
|
|
87
|
-
width: 20px;
|
|
88
|
-
height: 20px;
|
|
89
|
-
cursor: pointer;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.todo-text {
|
|
93
|
-
flex: 1;
|
|
94
|
-
font-size: 1rem;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.todo-text.done {
|
|
98
|
-
text-decoration: line-through;
|
|
99
|
-
color: #999;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.delete-btn {
|
|
103
|
-
padding: 0.25rem 0.5rem;
|
|
104
|
-
background: #f44336;
|
|
105
|
-
color: white;
|
|
106
|
-
border: none;
|
|
107
|
-
border-radius: 4px;
|
|
108
|
-
cursor: pointer;
|
|
109
|
-
font-size: 0.875rem;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.delete-btn:hover {
|
|
113
|
-
background: #da190b;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.stats {
|
|
117
|
-
margin-top: 1rem;
|
|
118
|
-
padding-top: 1rem;
|
|
119
|
-
border-top: 2px solid #f0f0f0;
|
|
120
|
-
display: flex;
|
|
121
|
-
justify-content: space-between;
|
|
122
|
-
color: #666;
|
|
123
|
-
font-size: 0.875rem;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
.clear-completed {
|
|
127
|
-
background: none;
|
|
128
|
-
border: none;
|
|
129
|
-
color: #f44336;
|
|
130
|
-
cursor: pointer;
|
|
131
|
-
text-decoration: underline;
|
|
132
|
-
}
|
|
133
|
-
</style>
|
|
134
|
-
|
|
135
|
-
<template>
|
|
136
|
-
<h1>📝 Todo App</h1>
|
|
137
|
-
|
|
138
|
-
<div class="input-group">
|
|
139
|
-
<input
|
|
140
|
-
type="text"
|
|
141
|
-
b-sync="newTodo"
|
|
142
|
-
placeholder="What needs to be done?"
|
|
143
|
-
@keyup="event.key === 'Enter' && addTodo()">
|
|
144
|
-
<button class="btn btn-primary" @click="addTodo()">Add</button>
|
|
145
|
-
</div>
|
|
146
|
-
|
|
147
|
-
<div class="filters">
|
|
148
|
-
<button
|
|
149
|
-
class="filter-btn {{ filter === 'all' ? 'active' : '' }}"
|
|
150
|
-
@click="filter = 'all'">
|
|
151
|
-
All
|
|
152
|
-
</button>
|
|
153
|
-
<button
|
|
154
|
-
class="filter-btn {{ filter === 'active' ? 'active' : '' }}"
|
|
155
|
-
@click="filter = 'active'">
|
|
156
|
-
Active
|
|
157
|
-
</button>
|
|
158
|
-
<button
|
|
159
|
-
class="filter-btn {{ filter === 'completed' ? 'active' : '' }}"
|
|
160
|
-
@click="filter = 'completed'">
|
|
161
|
-
Completed
|
|
162
|
-
</button>
|
|
163
|
-
</div>
|
|
164
|
-
|
|
165
|
-
<ul b-for="todo in filteredTodos()">
|
|
166
|
-
<li>
|
|
167
|
-
<input type="checkbox" b-sync="todo.done">
|
|
168
|
-
<span class="todo-text {{ todo.done ? 'done' : '' }}">
|
|
169
|
-
{{ todo.text }}
|
|
170
|
-
</span>
|
|
171
|
-
<button class="delete-btn" @click="deleteTodo(todo)">Delete</button>
|
|
172
|
-
</li>
|
|
173
|
-
</ul>
|
|
174
|
-
|
|
175
|
-
<div class="stats">
|
|
176
|
-
<span>{{ remaining() }} item{{ remaining() === 1 ? '' : 's' }} left</span>
|
|
177
|
-
<button
|
|
178
|
-
class="clear-completed"
|
|
179
|
-
b-show="completedCount() > 0"
|
|
180
|
-
@click="clearCompleted()">
|
|
181
|
-
Clear completed ({{ completedCount() }})
|
|
182
|
-
</button>
|
|
183
|
-
</div>
|
|
184
|
-
</template>
|
|
185
|
-
|
|
186
|
-
<script>
|
|
187
|
-
export default {
|
|
188
|
-
newTodo: '',
|
|
189
|
-
filter: 'all',
|
|
190
|
-
todos: [],
|
|
191
|
-
|
|
192
|
-
mounted() {
|
|
193
|
-
const saved = localStorage.getItem('legojs-todos');
|
|
194
|
-
if (saved) {
|
|
195
|
-
this.todos = JSON.parse(saved);
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
|
|
199
|
-
updated() {
|
|
200
|
-
localStorage.setItem('legojs-todos', JSON.stringify(this.todos));
|
|
201
|
-
},
|
|
202
|
-
|
|
203
|
-
addTodo() {
|
|
204
|
-
if (this.newTodo.trim()) {
|
|
205
|
-
this.todos.push({
|
|
206
|
-
id: Date.now(),
|
|
207
|
-
text: this.newTodo,
|
|
208
|
-
done: false
|
|
209
|
-
});
|
|
210
|
-
this.newTodo = '';
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
|
|
214
|
-
deleteTodo(todo) {
|
|
215
|
-
this.todos = this.todos.filter(t => t.id !== todo.id);
|
|
216
|
-
},
|
|
217
|
-
|
|
218
|
-
filteredTodos() {
|
|
219
|
-
if (this.filter === 'active') {
|
|
220
|
-
return this.todos.filter(t => !t.done);
|
|
221
|
-
} else if (this.filter === 'completed') {
|
|
222
|
-
return this.todos.filter(t => t.done);
|
|
223
|
-
}
|
|
224
|
-
return this.todos;
|
|
225
|
-
},
|
|
226
|
-
|
|
227
|
-
remaining() {
|
|
228
|
-
return this.todos.filter(t => !t.done).length;
|
|
229
|
-
},
|
|
230
|
-
|
|
231
|
-
completedCount() {
|
|
232
|
-
return this.todos.filter(t => t.done).length;
|
|
233
|
-
},
|
|
234
|
-
|
|
235
|
-
clearCompleted() {
|
|
236
|
-
this.todos = this.todos.filter(t => !t.done);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
</script>
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
<style>
|
|
2
|
-
self {
|
|
3
|
-
display: block;
|
|
4
|
-
padding: 1rem;
|
|
5
|
-
background: #f9fafb;
|
|
6
|
-
border-bottom: 1px solid #e0e0e0;
|
|
7
|
-
}
|
|
8
|
-
</style>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<template>
|
|
12
|
-
<div>
|
|
13
|
-
<h2>User Card</h2>
|
|
14
|
-
<p>Name: {{ name }}</p>
|
|
15
|
-
<p>Age: {{ age }}</p>
|
|
16
|
-
</div>
|
|
17
|
-
</template>
|
|
18
|
-
|
|
19
|
-
<script>
|
|
20
|
-
export default {
|
|
21
|
-
name: 'UserCard',
|
|
22
|
-
props: {
|
|
23
|
-
name: String,
|
|
24
|
-
age: Number
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
</script>
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vite';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
import legoPlugin from 'lego-dom/vite-plugin';
|
|
5
|
-
import tailwindcss from '@tailwindcss/vite';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export default defineConfig({
|
|
9
|
-
plugins: [
|
|
10
|
-
legoPlugin({
|
|
11
|
-
componentsDir: './src/components',
|
|
12
|
-
include: ['**/*.lego'],
|
|
13
|
-
importPath: 'lego-dom/main.js' // Use the alias defined below
|
|
14
|
-
}),
|
|
15
|
-
tailwindcss()
|
|
16
|
-
],
|
|
17
|
-
resolve: {
|
|
18
|
-
alias: {
|
|
19
|
-
'lego-dom/main.js': path.resolve(__dirname, '../../lego.js')
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
});
|
package/tests/error.test.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { JSDOM } from 'jsdom';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
// Setup Mock DOM
|
|
7
|
-
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
|
|
8
|
-
runScripts: "dangerously",
|
|
9
|
-
resources: "usable"
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
global.window = dom.window;
|
|
13
|
-
global.document = dom.window.document;
|
|
14
|
-
Object.defineProperty(global, 'navigator', {
|
|
15
|
-
value: dom.window.navigator,
|
|
16
|
-
writable: true,
|
|
17
|
-
configurable: true
|
|
18
|
-
});
|
|
19
|
-
global.HTMLElement = dom.window.HTMLElement;
|
|
20
|
-
global.customElements = dom.window.customElements;
|
|
21
|
-
global.MutationObserver = dom.window.MutationObserver;
|
|
22
|
-
global.Node = dom.window.Node;
|
|
23
|
-
global.NodeFilter = dom.window.NodeFilter;
|
|
24
|
-
global.Element = dom.window.Element;
|
|
25
|
-
global.Event = dom.window.Event;
|
|
26
|
-
global.requestAnimationFrame = (cb) => setTimeout(cb, 0);
|
|
27
|
-
|
|
28
|
-
const libCode = fs.readFileSync(path.resolve(__dirname, '../main.js'), 'utf8');
|
|
29
|
-
eval(libCode);
|
|
30
|
-
|
|
31
|
-
describe('LegoDOM Error Handling', () => {
|
|
32
|
-
beforeEach(async () => {
|
|
33
|
-
document.body.innerHTML = '';
|
|
34
|
-
await window.Lego.init(document.body);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should catch errors in rendering and call onError', async () => {
|
|
38
|
-
const errorSpy = vi.fn();
|
|
39
|
-
window.Lego.config.onError = errorSpy;
|
|
40
|
-
|
|
41
|
-
// Define a component that throws an error when accessing a property used in template
|
|
42
|
-
window.Lego.define('error-comp', '<div>[[ throwErr() ]]</div>', {
|
|
43
|
-
throwErr() {
|
|
44
|
-
throw new Error('Render Failure');
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const el = document.createElement('error-comp');
|
|
49
|
-
document.body.appendChild(el);
|
|
50
|
-
|
|
51
|
-
await new Promise(r => setTimeout(r, 100));
|
|
52
|
-
|
|
53
|
-
expect(errorSpy).toHaveBeenCalled();
|
|
54
|
-
expect(errorSpy.mock.calls[0][1]).toBe('render-error');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should catch errors in event handlers', async () => {
|
|
58
|
-
const errorSpy = vi.fn();
|
|
59
|
-
window.Lego.config.onError = errorSpy;
|
|
60
|
-
|
|
61
|
-
window.Lego.define('btn-error', '<button @click="crash()">Crash</button>', {
|
|
62
|
-
crash() { throw new Error('Boom'); }
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
const el = document.createElement('btn-error');
|
|
66
|
-
document.body.appendChild(el);
|
|
67
|
-
await new Promise(r => setTimeout(r, 100));
|
|
68
|
-
|
|
69
|
-
el.shadowRoot.querySelector('button').click();
|
|
70
|
-
|
|
71
|
-
expect(errorSpy).toHaveBeenCalled();
|
|
72
|
-
expect(errorSpy.mock.calls[0][1]).toBe('event-handler');
|
|
73
|
-
});
|
|
74
|
-
});
|
package/tests/main.test.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { JSDOM } from 'jsdom';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
// 1. Setup the DOM environment
|
|
7
|
-
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
|
|
8
|
-
runScripts: "dangerously",
|
|
9
|
-
resources: "usable"
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
global.window = dom.window;
|
|
13
|
-
global.document = dom.window.document;
|
|
14
|
-
|
|
15
|
-
// Use defineProperty for navigator as it might be read-only in some environments
|
|
16
|
-
Object.defineProperty(global, 'navigator', {
|
|
17
|
-
value: dom.window.navigator,
|
|
18
|
-
writable: true,
|
|
19
|
-
configurable: true
|
|
20
|
-
});
|
|
21
|
-
global.HTMLElement = dom.window.HTMLElement;
|
|
22
|
-
global.customElements = dom.window.customElements;
|
|
23
|
-
global.MutationObserver = dom.window.MutationObserver;
|
|
24
|
-
global.Node = dom.window.Node;
|
|
25
|
-
global.NodeFilter = dom.window.NodeFilter;
|
|
26
|
-
global.Element = dom.window.Element;
|
|
27
|
-
global.Event = dom.window.Event;
|
|
28
|
-
global.FormData = dom.window.FormData;
|
|
29
|
-
|
|
30
|
-
// Mock CSSStyleSheet for JSDOM
|
|
31
|
-
global.CSSStyleSheet = class {
|
|
32
|
-
constructor() { this.cssRules = []; }
|
|
33
|
-
replace(content) { this.cssText = content; return Promise.resolve(this); }
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
global.requestAnimationFrame = (cb) => setTimeout(cb, 0);
|
|
37
|
-
|
|
38
|
-
// 2. Load the library code
|
|
39
|
-
// We read it as a string to execute it in our shimmed global environment
|
|
40
|
-
const libCode = fs.readFileSync(path.resolve(__dirname, '../main.js'), 'utf8');
|
|
41
|
-
eval(libCode);
|
|
42
|
-
|
|
43
|
-
describe('Lego JS Node Environment Tests', () => {
|
|
44
|
-
beforeEach(async () => {
|
|
45
|
-
document.body.innerHTML = '';
|
|
46
|
-
window.clicked = false;
|
|
47
|
-
// Initializing the library manually for the test environment
|
|
48
|
-
await window.Lego.init(document.body);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should initialize the Lego global object', () => {
|
|
52
|
-
expect(window.Lego).toBeDefined();
|
|
53
|
-
expect(typeof window.Lego.define).toBe('function');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('should reactively update text content', async () => {
|
|
57
|
-
window.Lego.define('test-comp', '<span>[[msg]]</span>');
|
|
58
|
-
const el = document.createElement('test-comp');
|
|
59
|
-
el.setAttribute('b-data', "{ msg: 'hello' }");
|
|
60
|
-
document.body.appendChild(el);
|
|
61
|
-
|
|
62
|
-
// Wait for MutationObserver / snap to fire
|
|
63
|
-
await new Promise(r => setTimeout(r, 100));
|
|
64
|
-
|
|
65
|
-
const span = el.shadowRoot.querySelector('span');
|
|
66
|
-
expect(span.textContent).toBe('hello');
|
|
67
|
-
|
|
68
|
-
// Test reactivity
|
|
69
|
-
el._studs.msg = 'world';
|
|
70
|
-
|
|
71
|
-
// Wait for batcher (requestAnimationFrame shim)
|
|
72
|
-
await new Promise(r => setTimeout(r, 100));
|
|
73
|
-
expect(span.textContent).toBe('world');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('should prevent XSS via auto-escaping', async () => {
|
|
77
|
-
window.Lego.define('xss-comp', '<div>[[code]]</div>');
|
|
78
|
-
const el = document.createElement('xss-comp');
|
|
79
|
-
el.setAttribute('b-data', "{ code: '<script>alert(1)</script>' }");
|
|
80
|
-
document.body.appendChild(el);
|
|
81
|
-
|
|
82
|
-
await new Promise(r => setTimeout(r, 100));
|
|
83
|
-
|
|
84
|
-
const div = el.shadowRoot.querySelector('div');
|
|
85
|
-
// It should be escaped, not raw HTML
|
|
86
|
-
expect(div.innerHTML).toContain('<script>');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should handle @events using the universal binder', async () => {
|
|
90
|
-
window.Lego.define('event-comp', '<button @click="handleClick">Click Me</button>');
|
|
91
|
-
|
|
92
|
-
const el = document.createElement('event-comp');
|
|
93
|
-
el.setAttribute('b-data', `{ handleClick: () => { window.clicked = true; } }`);
|
|
94
|
-
document.body.appendChild(el);
|
|
95
|
-
|
|
96
|
-
await new Promise(r => setTimeout(r, 100));
|
|
97
|
-
|
|
98
|
-
const btn = el.shadowRoot.querySelector('button');
|
|
99
|
-
btn.click();
|
|
100
|
-
|
|
101
|
-
expect(window.clicked).toBe(true);
|
|
102
|
-
});
|
|
103
|
-
});
|
package/tests/memory.test.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { JSDOM } from 'jsdom';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
|
|
7
|
-
runScripts: "dangerously",
|
|
8
|
-
resources: "usable"
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
global.window = dom.window;
|
|
12
|
-
global.document = dom.window.document;
|
|
13
|
-
Object.defineProperty(global, 'navigator', {
|
|
14
|
-
value: dom.window.navigator,
|
|
15
|
-
writable: true,
|
|
16
|
-
configurable: true
|
|
17
|
-
});
|
|
18
|
-
global.HTMLElement = dom.window.HTMLElement;
|
|
19
|
-
global.customElements = dom.window.customElements;
|
|
20
|
-
global.MutationObserver = dom.window.MutationObserver;
|
|
21
|
-
global.Node = dom.window.Node;
|
|
22
|
-
global.NodeFilter = dom.window.NodeFilter;
|
|
23
|
-
global.Element = dom.window.Element;
|
|
24
|
-
global.Event = dom.window.Event;
|
|
25
|
-
global.requestAnimationFrame = (cb) => setTimeout(cb, 0);
|
|
26
|
-
|
|
27
|
-
const libCode = fs.readFileSync(path.resolve(__dirname, '../main.js'), 'utf8');
|
|
28
|
-
eval(libCode);
|
|
29
|
-
|
|
30
|
-
describe('LegoDOM Memory Management', () => {
|
|
31
|
-
beforeEach(async () => {
|
|
32
|
-
document.body.innerHTML = '';
|
|
33
|
-
await window.Lego.init(document.body);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should cleanup activeComponents when elements are removed', async () => {
|
|
37
|
-
window.Lego.define('mem-comp', '<div>Hi</div>');
|
|
38
|
-
|
|
39
|
-
// Mount
|
|
40
|
-
const el = document.createElement('mem-comp');
|
|
41
|
-
document.body.appendChild(el);
|
|
42
|
-
await new Promise(r => setTimeout(r, 100)); // Wait for observer
|
|
43
|
-
|
|
44
|
-
expect(window.Lego.getActiveComponentsCount()).toBe(1);
|
|
45
|
-
|
|
46
|
-
// Unmount
|
|
47
|
-
el.remove();
|
|
48
|
-
await new Promise(r => setTimeout(r, 100)); // Wait for observer
|
|
49
|
-
|
|
50
|
-
expect(window.Lego.getActiveComponentsCount()).toBe(0);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should cleanup nested components recursively', async () => {
|
|
54
|
-
window.Lego.define('parent-comp', '<child-comp></child-comp>');
|
|
55
|
-
window.Lego.define('child-comp', '<span>Child</span>');
|
|
56
|
-
|
|
57
|
-
const el = document.createElement('parent-comp');
|
|
58
|
-
document.body.appendChild(el);
|
|
59
|
-
await new Promise(r => setTimeout(r, 100));
|
|
60
|
-
|
|
61
|
-
expect(window.Lego.getActiveComponentsCount()).toBe(2); // Parent + Child
|
|
62
|
-
|
|
63
|
-
el.remove();
|
|
64
|
-
await new Promise(r => setTimeout(r, 100));
|
|
65
|
-
|
|
66
|
-
expect(window.Lego.getActiveComponentsCount()).toBe(0);
|
|
67
|
-
});
|
|
68
|
-
});
|
package/tests/monitoring.test.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { JSDOM } from 'jsdom';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { Monitoring } from '../monitoring-plugin.js';
|
|
6
|
-
|
|
7
|
-
// Setup Mock DOM with Performance API
|
|
8
|
-
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
|
|
9
|
-
runScripts: "dangerously",
|
|
10
|
-
resources: "usable"
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
global.window = dom.window;
|
|
14
|
-
global.document = dom.window.document;
|
|
15
|
-
global.performance = {
|
|
16
|
-
mark: vi.fn(),
|
|
17
|
-
measure: vi.fn(),
|
|
18
|
-
getEntriesByName: vi.fn(() => [{ duration: 20 }]), // Mock 20ms render
|
|
19
|
-
clearMarks: vi.fn(),
|
|
20
|
-
clearMeasures: vi.fn()
|
|
21
|
-
};
|
|
22
|
-
global.HTMLElement = dom.window.HTMLElement;
|
|
23
|
-
global.customElements = dom.window.customElements;
|
|
24
|
-
global.MutationObserver = dom.window.MutationObserver;
|
|
25
|
-
global.Node = dom.window.Node;
|
|
26
|
-
global.NodeFilter = dom.window.NodeFilter;
|
|
27
|
-
global.Element = dom.window.Element;
|
|
28
|
-
global.Event = dom.window.Event;
|
|
29
|
-
global.requestAnimationFrame = (cb) => setTimeout(cb, 0);
|
|
30
|
-
|
|
31
|
-
const libCode = fs.readFileSync(path.resolve(__dirname, '../main.js'), 'utf8');
|
|
32
|
-
eval(libCode);
|
|
33
|
-
|
|
34
|
-
describe('LegoDOM Monitoring Plugin', () => {
|
|
35
|
-
beforeEach(async () => {
|
|
36
|
-
document.body.innerHTML = '';
|
|
37
|
-
await window.Lego.init(document.body);
|
|
38
|
-
Monitoring.install(window.Lego, { reportToConsole: false });
|
|
39
|
-
window.Lego.metrics.reset();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should track render count and duration', async () => {
|
|
43
|
-
window.Lego.define('monitor-test', '<div>[[ msg ]]</div>');
|
|
44
|
-
const el = document.createElement('monitor-test');
|
|
45
|
-
el.setAttribute('b-data', "{ msg: 'hello' }");
|
|
46
|
-
document.body.appendChild(el);
|
|
47
|
-
|
|
48
|
-
await new Promise(r => setTimeout(r, 100));
|
|
49
|
-
|
|
50
|
-
const metrics = window.Lego.metrics.get();
|
|
51
|
-
expect(metrics.renders).toBeGreaterThan(0);
|
|
52
|
-
|
|
53
|
-
const stats = metrics.components.get('monitor-test');
|
|
54
|
-
expect(stats).toBeDefined();
|
|
55
|
-
expect(stats.count).toBe(1);
|
|
56
|
-
// Since we mocked getEntriesByName to return 20ms
|
|
57
|
-
expect(stats.avg).toBe(20);
|
|
58
|
-
expect(metrics.slowRenders).toBe(1); // 20ms > 16ms threshold
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should track errors via the hooked onError handler', async () => {
|
|
62
|
-
window.Lego.define('monitor-error', '<div>[[ throwErr() ]]</div>', {
|
|
63
|
-
throwErr() { throw new Error('Monitor Fail'); }
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const el = document.createElement('monitor-error');
|
|
67
|
-
document.body.appendChild(el);
|
|
68
|
-
|
|
69
|
-
await new Promise(r => setTimeout(r, 100));
|
|
70
|
-
|
|
71
|
-
const metrics = window.Lego.metrics.get();
|
|
72
|
-
expect(metrics.errors).toBe(1);
|
|
73
|
-
});
|
|
74
|
-
});
|
package/tests/naming.test.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { JSDOM } from 'jsdom';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
|
|
7
|
-
runScripts: "dangerously",
|
|
8
|
-
resources: "usable"
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
global.window = dom.window;
|
|
12
|
-
global.document = dom.window.document;
|
|
13
|
-
Object.defineProperty(global, 'navigator', { value: dom.window.navigator });
|
|
14
|
-
global.HTMLElement = dom.window.HTMLElement;
|
|
15
|
-
global.customElements = dom.window.customElements;
|
|
16
|
-
global.MutationObserver = dom.window.MutationObserver;
|
|
17
|
-
global.Node = dom.window.Node;
|
|
18
|
-
global.NodeFilter = dom.window.NodeFilter;
|
|
19
|
-
global.Element = dom.window.Element;
|
|
20
|
-
global.Event = dom.window.Event;
|
|
21
|
-
global.fetch = vi.fn();
|
|
22
|
-
global.requestAnimationFrame = (cb) => setTimeout(cb, 0);
|
|
23
|
-
|
|
24
|
-
const libCode = fs.readFileSync(path.resolve(__dirname, '../main.js'), 'utf8');
|
|
25
|
-
eval(libCode);
|
|
26
|
-
|
|
27
|
-
describe('Component Naming Policy', () => {
|
|
28
|
-
beforeEach(() => {
|
|
29
|
-
document.body.innerHTML = '';
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const define = (filename) => {
|
|
33
|
-
const sfc = `<template><div>Test</div></template>`;
|
|
34
|
-
window.Lego.defineSFC(sfc, filename);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
it('should support kebab-case (standard)', () => {
|
|
38
|
-
define('user-card.lego');
|
|
39
|
-
expect(document.querySelector('user-card')).toBeDefined();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should auto-convert PascalCase to kebab-case', () => {
|
|
43
|
-
define('UserProfile.lego');
|
|
44
|
-
const el = document.createElement('user-profile');
|
|
45
|
-
document.body.appendChild(el);
|
|
46
|
-
expect(el.shadowRoot).toBeDefined();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should auto-convert camelCase to kebab-case', () => {
|
|
50
|
-
define('navBar.lego');
|
|
51
|
-
const el = document.createElement('nav-bar');
|
|
52
|
-
document.body.appendChild(el);
|
|
53
|
-
expect(el.shadowRoot).toBeDefined();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('should auto-convert snake_case to kebab-case', () => {
|
|
57
|
-
define('data_table.lego');
|
|
58
|
-
const el = document.createElement('data-table');
|
|
59
|
-
document.body.appendChild(el);
|
|
60
|
-
expect(el.shadowRoot).toBeDefined();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should throw error for single-word PascalCase (no hyphen result)', () => {
|
|
64
|
-
expect(() => define('Button.lego')).toThrow(/Invalid component definition/);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should throw error for single-word lowercase', () => {
|
|
68
|
-
expect(() => define('table.lego')).toThrow(/Invalid component definition/);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('should throw error for single-word brand name', () => {
|
|
72
|
-
expect(() => define('adidas.lego')).toThrow(/Invalid component definition/);
|
|
73
|
-
});
|
|
74
|
-
});
|