slicejs-cli 3.5.1 → 3.6.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/README.md +34 -15
- package/client.js +67 -20
- package/commands/doctor/doctor.js +69 -3
- package/commands/getComponent/getComponent.js +33 -25
- package/commands/init/init.js +106 -28
- package/commands/utils/PackageManager.js +148 -0
- package/commands/utils/VersionChecker.js +6 -4
- package/commands/utils/sliceScripts.js +21 -0
- package/commands/utils/updateManager.js +54 -35
- package/package.json +12 -1
- package/post.js +8 -16
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -29
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -25
- package/.github/pull_request_template.md +0 -22
- package/.github/workflows/ci.yml +0 -43
- package/AGENTS.md +0 -247
- package/CODE_OF_CONDUCT.md +0 -126
- package/ECOSYSTEM.md +0 -9
- package/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +0 -182
- package/playwright.config.js +0 -51
- package/tests/build-command-integration.test.js +0 -87
- package/tests/build-production-e2e.test.js +0 -140
- package/tests/builder-edge-cases.test.js +0 -322
- package/tests/bundle-generate-e2e.test.js +0 -115
- package/tests/bundle-generator.test.js +0 -691
- package/tests/bundle-v2-register-output.test.js +0 -470
- package/tests/bundling-dependency-edges.test.js +0 -127
- package/tests/bundling-imports-unit.test.js +0 -267
- package/tests/client-launcher-contract.test.js +0 -211
- package/tests/client-update-flow-contract.test.js +0 -272
- package/tests/commands-component-crud.test.js +0 -102
- package/tests/commands-doctor.test.js +0 -80
- package/tests/commands-version-checker.test.js +0 -37
- package/tests/component-registry-parse.test.js +0 -34
- package/tests/dependency-analyzer.test.js +0 -24
- package/tests/e2e/bundles.spec.js +0 -91
- package/tests/e2e/dependency-scenarios.spec.js +0 -56
- package/tests/e2e/fixtures/components/Service/FetchManager/FetchManager.js +0 -136
- package/tests/e2e/fixtures/components/Service/IndexedDbManager/IndexedDbManager.js +0 -149
- package/tests/e2e/fixtures/components/Service/LocalStorageManager/LocalStorageManager.js +0 -45
- package/tests/e2e/fixtures/components/Visual/Button/Button.css +0 -106
- package/tests/e2e/fixtures/components/Visual/Button/Button.html +0 -5
- package/tests/e2e/fixtures/components/Visual/Button/Button.js +0 -158
- package/tests/e2e/fixtures/components/Visual/Link/Link.js +0 -33
- package/tests/e2e/fixtures/components/Visual/Loading/Loading.css +0 -56
- package/tests/e2e/fixtures/components/Visual/Loading/Loading.html +0 -83
- package/tests/e2e/fixtures/components/Visual/Loading/Loading.js +0 -164
- package/tests/e2e/fixtures/components/Visual/MultiRoute/MultiRoute.js +0 -167
- package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.css +0 -116
- package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.html +0 -44
- package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.js +0 -180
- package/tests/e2e/fixtures/components/Visual/NotFound/NotFound.js +0 -20
- package/tests/e2e/fixtures/components/Visual/Route/Route.js +0 -181
- package/tests/e2e/fixtures/components/registry.json +0 -12
- package/tests/e2e/fixtures/vendor-components.mjs +0 -65
- package/tests/e2e/navigation.spec.js +0 -44
- package/tests/e2e/render.spec.js +0 -34
- package/tests/e2e/serve.mjs +0 -264
- package/tests/e2e/shared-deps.spec.js +0 -61
- package/tests/e2e/unminified.spec.js +0 -33
- package/tests/e2e-serve.test.js +0 -148
- package/tests/fixtures/components.js +0 -8
- package/tests/fixtures/sliceConfig.json +0 -74
- package/tests/getcomponent.test.js +0 -407
- package/tests/helpers/setup.js +0 -102
- package/tests/init-command-contract.test.js +0 -46
- package/tests/local-cli-delegation.test.js +0 -81
- package/tests/path-helper.test.js +0 -206
- package/tests/perf-budget.test.js +0 -86
- package/tests/postinstall-command.test.js +0 -72
- package/tests/types-breakage.test.js +0 -491
- package/tests/types-generator-errors.test.js +0 -361
- package/tests/types-generator.test.js +0 -346
- package/tests/update-manager-notifications.test.js +0 -88
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
<header class="slice_nav_header">
|
|
2
|
-
<a class="logo_container"></a>
|
|
3
|
-
<nav class="slice_nav_bar">
|
|
4
|
-
<div class="nav_bar_menu"></div>
|
|
5
|
-
<div class="nav_bar_buttons"></div>
|
|
6
|
-
<div class="mobile_close_menu">
|
|
7
|
-
<svg
|
|
8
|
-
class="w-6 h-6 text-gray-800 dark:text-white"
|
|
9
|
-
aria-hidden="true"
|
|
10
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
11
|
-
width="24"
|
|
12
|
-
height="24"
|
|
13
|
-
fill="none"
|
|
14
|
-
viewBox="0 0 24 24"
|
|
15
|
-
>
|
|
16
|
-
<path
|
|
17
|
-
stroke="var(--primary-color-contrast)"
|
|
18
|
-
stroke-linecap="round"
|
|
19
|
-
stroke-linejoin="round"
|
|
20
|
-
stroke-width="2"
|
|
21
|
-
d="M6 18 17.94 6M18 18 6.06 6"
|
|
22
|
-
/>
|
|
23
|
-
</svg>
|
|
24
|
-
</div>
|
|
25
|
-
</nav>
|
|
26
|
-
<div class="mobile_menu_button">
|
|
27
|
-
<svg
|
|
28
|
-
class="w-6 h-6 text-gray-800 dark:text-white"
|
|
29
|
-
aria-hidden="true"
|
|
30
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
31
|
-
width="24"
|
|
32
|
-
height="24"
|
|
33
|
-
fill="none"
|
|
34
|
-
viewBox="0 0 24 24"
|
|
35
|
-
>
|
|
36
|
-
<path
|
|
37
|
-
stroke="var(--primary-color-contrast)"
|
|
38
|
-
stroke-linecap="round"
|
|
39
|
-
stroke-width="3"
|
|
40
|
-
d="M12 6h.01M12 12h.01M12 18h.01"
|
|
41
|
-
/>
|
|
42
|
-
</svg>
|
|
43
|
-
</div>
|
|
44
|
-
</header>
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
export default class Navbar extends HTMLElement {
|
|
2
|
-
|
|
3
|
-
static props = {
|
|
4
|
-
logo: {
|
|
5
|
-
type: 'object',
|
|
6
|
-
default: null,
|
|
7
|
-
required: false
|
|
8
|
-
},
|
|
9
|
-
items: {
|
|
10
|
-
type: 'array',
|
|
11
|
-
default: [],
|
|
12
|
-
required: false
|
|
13
|
-
},
|
|
14
|
-
buttons: {
|
|
15
|
-
type: 'array',
|
|
16
|
-
default: [],
|
|
17
|
-
required: false
|
|
18
|
-
},
|
|
19
|
-
position: {
|
|
20
|
-
type: 'string',
|
|
21
|
-
default: 'static',
|
|
22
|
-
required: false
|
|
23
|
-
},
|
|
24
|
-
direction: {
|
|
25
|
-
type: 'string',
|
|
26
|
-
default: 'normal',
|
|
27
|
-
required: false
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
constructor(props) {
|
|
32
|
-
super();
|
|
33
|
-
slice.attachTemplate(this);
|
|
34
|
-
|
|
35
|
-
this.$header = this.querySelector('.slice_nav_header');
|
|
36
|
-
this.$navBar = this.querySelector('.slice_nav_bar');
|
|
37
|
-
this.$menu = this.querySelector('.nav_bar_menu');
|
|
38
|
-
this.$buttonsContainer = this.querySelector('.nav_bar_buttons');
|
|
39
|
-
this.$logoContainer = this.querySelector('.logo_container');
|
|
40
|
-
this.$mobileMenu = this.querySelector('.slice_mobile_menu');
|
|
41
|
-
this.$mobileButton = this.querySelector('.mobile_menu_button');
|
|
42
|
-
this.$closeMenu = this.querySelector('.mobile_close_menu');
|
|
43
|
-
|
|
44
|
-
this.$mobileButton.addEventListener('click', () => {
|
|
45
|
-
this.$navBar.style.transform = 'translateX(0%)';
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
this.$closeMenu.addEventListener('click', () => {
|
|
49
|
-
this.$navBar.style.transform = 'translateX(100%)';
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
slice.controller.setComponentProps(this, props);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async init() {
|
|
56
|
-
if (this.items && this.items.length > 0) {
|
|
57
|
-
await this.addItems(this.items);
|
|
58
|
-
}
|
|
59
|
-
if (this.buttons && this.buttons.length > 0) {
|
|
60
|
-
this.buttons.forEach(async (item) => {
|
|
61
|
-
await this.addButton(item, this.$buttonsContainer);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (window.screen.width >= 1020 && this.items && this.logo && this.buttons) {
|
|
66
|
-
this.$menu.style.maxWidth = '60%';
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async addItems(items) {
|
|
71
|
-
for (let i = 0; i < items.length; i++) {
|
|
72
|
-
await this.addItem(items[i], this.$menu);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
get position() {
|
|
77
|
-
return this._position;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
set position(value) {
|
|
81
|
-
this._position = value;
|
|
82
|
-
if (value === 'fixed') {
|
|
83
|
-
this.classList.add('nav_bar_fixed');
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
get logo() {
|
|
88
|
-
return this._logo;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
set logo(value) {
|
|
92
|
-
this._logo = value;
|
|
93
|
-
// ✅ CORREGIDO: Validar que value no sea null antes de usarlo
|
|
94
|
-
if (!value) return;
|
|
95
|
-
|
|
96
|
-
const img = document.createElement('img');
|
|
97
|
-
img.src = value.src;
|
|
98
|
-
this.$logoContainer.appendChild(img);
|
|
99
|
-
this.$logoContainer.href = value.path;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
get items() {
|
|
103
|
-
return this._items;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
set items(values) {
|
|
107
|
-
this._items = values;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
get buttons() {
|
|
111
|
-
return this._buttons;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
set buttons(values) {
|
|
115
|
-
this._buttons = values;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
get direction() {
|
|
119
|
-
return this._direction;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
set direction(value) {
|
|
123
|
-
this._direction = value;
|
|
124
|
-
// ✅ MEJORADO: Validar valor antes de aplicar clase
|
|
125
|
-
if (value === 'reverse') {
|
|
126
|
-
this.$header.classList.add('direction-row-reverse');
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async addItem(value, addTo) {
|
|
131
|
-
const item = document.createElement('li');
|
|
132
|
-
const hover = document.createElement('div');
|
|
133
|
-
hover.classList.add('anim-item');
|
|
134
|
-
|
|
135
|
-
if (!value.type) {
|
|
136
|
-
value.type = 'text';
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (value.type === 'text') {
|
|
140
|
-
const link = await slice.build('Link', {
|
|
141
|
-
text: value.text,
|
|
142
|
-
path: value.path,
|
|
143
|
-
classes: 'item',
|
|
144
|
-
});
|
|
145
|
-
item.appendChild(link);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (value.type === 'dropdown') {
|
|
149
|
-
const d = await slice.build('DropDown', {
|
|
150
|
-
label: value.text,
|
|
151
|
-
options: value.options,
|
|
152
|
-
});
|
|
153
|
-
d.classList.add('item');
|
|
154
|
-
item.appendChild(d);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
item.appendChild(hover);
|
|
158
|
-
addTo.appendChild(item);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async addButton(value, addTo) {
|
|
162
|
-
if (!value.color) {
|
|
163
|
-
value.color = {
|
|
164
|
-
text: 'var(--primary-color-rgb)',
|
|
165
|
-
background: 'var(--primary-background-color)',
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const button = await slice.build('Button', {
|
|
170
|
-
value: value.value,
|
|
171
|
-
customColor: value.color,
|
|
172
|
-
icon: value.icon,
|
|
173
|
-
onClick: value.onClick ?? value.onClickCallback,
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
addTo.appendChild(button);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
window.customElements.define('slice-nav-bar', Navbar);
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export default class NotFound extends HTMLElement {
|
|
2
|
-
|
|
3
|
-
static props = {
|
|
4
|
-
// No props needed for this component
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
constructor(props) {
|
|
8
|
-
super();
|
|
9
|
-
slice.attachTemplate(this);
|
|
10
|
-
|
|
11
|
-
slice.controller.setComponentProps(this, props);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
init() {
|
|
15
|
-
//change title of the page
|
|
16
|
-
document.title = '404 - Not Found';
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
customElements.define('slice-notfound', NotFound);
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
export default class Route extends HTMLElement {
|
|
2
|
-
constructor(props) {
|
|
3
|
-
super();
|
|
4
|
-
this.props = props;
|
|
5
|
-
this.rendered = false;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
init() {
|
|
9
|
-
if (!this.props.path) {
|
|
10
|
-
this.props.path = ' ';
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// If no component is given, derive it from routes.js (the Router's pathToRouteMap).
|
|
14
|
-
if (!this.props.component) {
|
|
15
|
-
this.props.component = slice.router.pathToRouteMap.get(this.props.path)?.component || ' ';
|
|
16
|
-
}
|
|
17
|
-
// NOTE: Route does NOT register itself in the Router. `routes.js` is the single source of
|
|
18
|
-
// truth for what the Router knows. The Router resolves the URL on first load / refresh /
|
|
19
|
-
// deep-link BEFORE this component mounts, so a path that only lived in a Route would 404
|
|
20
|
-
// on a direct load. Declare every path in `routes.js`.
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
get path() {
|
|
24
|
-
return this.props.path;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
set path(value) {
|
|
28
|
-
this.props.path = value;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
get component() {
|
|
32
|
-
return this.props.component;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
set component(value) {
|
|
36
|
-
this.props.component = value;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Verifica si el path actual coincide con el path del Route
|
|
41
|
-
* Soporta rutas estáticas y dinámicas con parámetros ${param}
|
|
42
|
-
*/
|
|
43
|
-
matchesCurrentPath() {
|
|
44
|
-
// Normalize trailing slash so '/about/' behaves like '/about' (keep root '/').
|
|
45
|
-
const raw = window.location.pathname;
|
|
46
|
-
const currentPath = raw.length > 1 ? raw.replace(/\/+$/, '') : raw;
|
|
47
|
-
const routePath = this.props.path.length > 1 ? this.props.path.replace(/\/+$/, '') : this.props.path;
|
|
48
|
-
|
|
49
|
-
// 1. Match exacto, case-insensitive ('/About' coincide con '/about')
|
|
50
|
-
if (routePath.toLowerCase() === currentPath.toLowerCase()) {
|
|
51
|
-
return { matches: true, params: {} };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// 2. Si la ruta tiene parámetros dinámicos ${param}
|
|
55
|
-
if (this.props.path.includes('${')) {
|
|
56
|
-
const { regex, paramNames } = this.compilePathPattern(this.props.path);
|
|
57
|
-
const match = currentPath.match(regex);
|
|
58
|
-
|
|
59
|
-
if (match) {
|
|
60
|
-
// Extraer parámetros de la URL
|
|
61
|
-
const params = {};
|
|
62
|
-
paramNames.forEach((name, i) => {
|
|
63
|
-
params[name] = match[i + 1];
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
return { matches: true, params };
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return { matches: false, params: {} };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Convierte un patrón de ruta con ${param} en una expresión regular
|
|
75
|
-
* Ejemplo: "/user/${id}" -> /^\/user\/([^/]+)$/
|
|
76
|
-
*/
|
|
77
|
-
compilePathPattern(pattern) {
|
|
78
|
-
const paramNames = [];
|
|
79
|
-
const regexPattern = '^' + pattern.replace(/\$\{([^}]+)\}/g, (_, paramName) => {
|
|
80
|
-
paramNames.push(paramName);
|
|
81
|
-
return '([^/]+)'; // Captura cualquier caracter excepto /
|
|
82
|
-
}) + '$';
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
// 'i': case-insensitive path matching. Captured param values keep their original case.
|
|
86
|
-
regex: new RegExp(regexPattern, 'i'),
|
|
87
|
-
paramNames
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async render(params = {}) {
|
|
92
|
-
const metadata = this.props.metadata || {};
|
|
93
|
-
|
|
94
|
-
if (Route.componentCache[this.props.component]) {
|
|
95
|
-
const cachedComponent = Route.componentCache[this.props.component];
|
|
96
|
-
this.innerHTML = '';
|
|
97
|
-
|
|
98
|
-
// Actualizar props del componente cacheado
|
|
99
|
-
if (cachedComponent.props) {
|
|
100
|
-
cachedComponent.props = {
|
|
101
|
-
...cachedComponent.props,
|
|
102
|
-
params: params,
|
|
103
|
-
metadata: metadata // ✅ Incluir metadata
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (cachedComponent.update) {
|
|
108
|
-
await cachedComponent.update();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
this.appendChild(cachedComponent);
|
|
112
|
-
} else {
|
|
113
|
-
if (!this.props.component) {
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (!slice.controller.componentCategories.has(this.props.component)) {
|
|
118
|
-
slice.logger.logError(`${this.sliceId}`, `Component ${this.props.component} not found`);
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Crear el componente con los parámetros y metadata de la ruta
|
|
123
|
-
const component = await slice.build(this.props.component, {
|
|
124
|
-
sliceId: this.props.component,
|
|
125
|
-
params: params, // ✅ Pasar los parámetros al componente
|
|
126
|
-
metadata: metadata // ✅ Pasar metadata al componente
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
this.innerHTML = '';
|
|
130
|
-
this.appendChild(component);
|
|
131
|
-
Route.componentCache[this.props.component] = component;
|
|
132
|
-
}
|
|
133
|
-
this.rendered = true;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async renderIfCurrentRoute() {
|
|
137
|
-
const { matches, params } = this.matchesCurrentPath();
|
|
138
|
-
|
|
139
|
-
if (matches) {
|
|
140
|
-
if (this.rendered) {
|
|
141
|
-
if (Route.componentCache[this.props.component]) {
|
|
142
|
-
const cachedComponent = Route.componentCache[this.props.component];
|
|
143
|
-
|
|
144
|
-
// Actualizar params y metadata en el componente cacheado
|
|
145
|
-
if (cachedComponent.props) {
|
|
146
|
-
cachedComponent.props = {
|
|
147
|
-
...cachedComponent.props,
|
|
148
|
-
params: params,
|
|
149
|
-
metadata: this.props.metadata || {}
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (cachedComponent.update) {
|
|
154
|
-
await cachedComponent.update();
|
|
155
|
-
}
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
await this.render(params);
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
removeComponent() {
|
|
166
|
-
delete Route.componentCache[this.props.component];
|
|
167
|
-
this.innerHTML = '';
|
|
168
|
-
this.rendered = false;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Cleanup cuando el componente se destruye
|
|
173
|
-
*/
|
|
174
|
-
destroy() {
|
|
175
|
-
this.removeComponent();
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
Route.componentCache = {};
|
|
180
|
-
|
|
181
|
-
customElements.define('slice-route', Route);
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"Button": "Visual",
|
|
3
|
-
"Link": "Visual",
|
|
4
|
-
"Loading": "Visual",
|
|
5
|
-
"MultiRoute": "Visual",
|
|
6
|
-
"Navbar": "Visual",
|
|
7
|
-
"NotFound": "Visual",
|
|
8
|
-
"Route": "Visual",
|
|
9
|
-
"FetchManager": "Service",
|
|
10
|
-
"IndexedDbManager": "Service",
|
|
11
|
-
"LocalStorageManager": "Service"
|
|
12
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
// One-time dev utility: downloads the starter Visual/Service components from the
|
|
2
|
-
// official Slice.js visual library and persists them under ./components so the
|
|
3
|
-
// browser E2E can assemble a complete, renderable starter app hermetically
|
|
4
|
-
// (no network at test time). Re-run with `node tests/e2e/fixtures/vendor-components.mjs`
|
|
5
|
-
// to refresh the fixture.
|
|
6
|
-
import fs from 'fs-extra';
|
|
7
|
-
import path from 'node:path';
|
|
8
|
-
import { fileURLToPath } from 'node:url';
|
|
9
|
-
|
|
10
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
const OUT = path.join(__dirname, 'components');
|
|
12
|
-
const BASE = 'https://raw.githubusercontent.com/VKneider/slice.js_visual_library/master/src/Components';
|
|
13
|
-
|
|
14
|
-
// Mirrors the starter set installed by `slice init`.
|
|
15
|
-
const VISUAL = ['Button', 'Link', 'Loading', 'MultiRoute', 'Navbar', 'NotFound', 'Route'];
|
|
16
|
-
const SERVICE = ['FetchManager', 'IndexedDbManager', 'LocalStorageManager'];
|
|
17
|
-
// Logical routing components ship JS-only (mirrors getAvailableComponents()).
|
|
18
|
-
const JS_ONLY = new Set(['Route', 'MultiRoute', 'Link']);
|
|
19
|
-
|
|
20
|
-
function filesFor(name, category) {
|
|
21
|
-
if (category === 'Service' || JS_ONLY.has(name)) return [`${name}.js`];
|
|
22
|
-
return [`${name}.js`, `${name}.html`, `${name}.css`];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function fetchText(url) {
|
|
26
|
-
const res = await fetch(url);
|
|
27
|
-
if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
|
|
28
|
-
return res.text();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function vendor(name, category) {
|
|
32
|
-
const dir = path.join(OUT, category, name);
|
|
33
|
-
await fs.ensureDir(dir);
|
|
34
|
-
for (const file of filesFor(name, category)) {
|
|
35
|
-
const required = file.endsWith('.js');
|
|
36
|
-
try {
|
|
37
|
-
const content = await fetchText(`${BASE}/${category}/${name}/${file}`);
|
|
38
|
-
await fs.writeFile(path.join(dir, file), content, 'utf8');
|
|
39
|
-
process.stdout.write(` ✓ ${category}/${name}/${file} (${content.length}b)\n`);
|
|
40
|
-
} catch (err) {
|
|
41
|
-
if (required) throw err;
|
|
42
|
-
// Optional html/css may not exist for simple components (e.g. NotFound).
|
|
43
|
-
process.stdout.write(` - ${category}/${name}/${file} (skipped: ${err.message.split(' for ')[0]})\n`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function main() {
|
|
49
|
-
await fs.emptyDir(OUT);
|
|
50
|
-
for (const name of VISUAL) await vendor(name, 'Visual');
|
|
51
|
-
for (const name of SERVICE) await vendor(name, 'Service');
|
|
52
|
-
|
|
53
|
-
// Persist a registry manifest so the harness can register them in components.js.
|
|
54
|
-
const registry = {};
|
|
55
|
-
for (const name of VISUAL) registry[name] = 'Visual';
|
|
56
|
-
for (const name of SERVICE) registry[name] = 'Service';
|
|
57
|
-
await fs.writeJson(path.join(OUT, 'registry.json'), registry, { spaces: 2 });
|
|
58
|
-
|
|
59
|
-
console.log(`\nVendored ${VISUAL.length} Visual + ${SERVICE.length} Service components into ${OUT}`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
main().catch((err) => {
|
|
63
|
-
console.error(err.message);
|
|
64
|
-
process.exit(1);
|
|
65
|
-
});
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { test, expect } from '@playwright/test';
|
|
2
|
-
|
|
3
|
-
test.describe('navigation (App Shell + MultiRoute)', () => {
|
|
4
|
-
test('navigates Home -> About via the in-page Button', async ({ page }) => {
|
|
5
|
-
await page.goto('/');
|
|
6
|
-
await expect(page.locator('slice-home-section h1.home__title')).toBeVisible();
|
|
7
|
-
|
|
8
|
-
await page.locator('slice-home-section slice-button button').click();
|
|
9
|
-
|
|
10
|
-
await expect(page).toHaveURL(/\/about$/);
|
|
11
|
-
await expect(page.locator('slice-about-section h1')).toHaveText('About');
|
|
12
|
-
// The shell (navbar) persists across the content swap.
|
|
13
|
-
await expect(page.locator('slice-nav-bar')).toBeAttached();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test('deep-links directly to /about', async ({ page }) => {
|
|
17
|
-
await page.goto('/about');
|
|
18
|
-
await expect(page.locator('slice-app-shell')).toBeAttached();
|
|
19
|
-
await expect(page.locator('slice-about-section h1')).toHaveText('About');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('renders the NotFound page for the /404 route', async ({ page }) => {
|
|
23
|
-
await page.goto('/404');
|
|
24
|
-
await expect(page.locator('slice-notfound')).toBeAttached();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('navbar links swap sections while keeping the shell mounted', async ({ page }) => {
|
|
28
|
-
await page.goto('/about');
|
|
29
|
-
await expect(page.locator('slice-about-section')).toBeAttached();
|
|
30
|
-
|
|
31
|
-
await page.locator('slice-nav-bar').getByText('Home', { exact: true }).click();
|
|
32
|
-
|
|
33
|
-
await expect(page).toHaveURL(/127\.0\.0\.1:\d+\/$/);
|
|
34
|
-
await expect(page.locator('slice-home-section')).toBeAttached();
|
|
35
|
-
await expect(page.locator('slice-nav-bar')).toBeAttached();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('survives a full reload on a deep route', async ({ page }) => {
|
|
39
|
-
await page.goto('/about');
|
|
40
|
-
await expect(page.locator('slice-about-section')).toBeAttached();
|
|
41
|
-
await page.reload();
|
|
42
|
-
await expect(page.locator('slice-about-section h1')).toHaveText('About');
|
|
43
|
-
});
|
|
44
|
-
});
|
package/tests/e2e/render.spec.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { test, expect } from '@playwright/test';
|
|
2
|
-
|
|
3
|
-
// Collect console errors / uncaught page errors for the whole test.
|
|
4
|
-
function trackErrors(page) {
|
|
5
|
-
const errors = [];
|
|
6
|
-
page.on('console', (msg) => {
|
|
7
|
-
if (msg.type() === 'error') errors.push(msg.text());
|
|
8
|
-
});
|
|
9
|
-
page.on('pageerror', (err) => errors.push(String(err)));
|
|
10
|
-
return errors;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
test.describe('initial app render (production build)', () => {
|
|
14
|
-
test('boots the framework and renders the home page with no console errors', async ({ page }) => {
|
|
15
|
-
const errors = trackErrors(page);
|
|
16
|
-
|
|
17
|
-
await page.goto('/');
|
|
18
|
-
|
|
19
|
-
// The App Shell mounts, with its persistent navbar.
|
|
20
|
-
await expect(page.locator('slice-app-shell')).toBeAttached();
|
|
21
|
-
await expect(page.locator('slice-nav-bar')).toBeAttached();
|
|
22
|
-
|
|
23
|
-
// The Home section renders its real content...
|
|
24
|
-
await expect(page.locator('slice-home-section h1.home__title')).toHaveText(
|
|
25
|
-
/Welcome to your Slice app/
|
|
26
|
-
);
|
|
27
|
-
// ...including a child Button built via slice.build with its label.
|
|
28
|
-
await expect(
|
|
29
|
-
page.locator('slice-home-section slice-button .slice_button_value')
|
|
30
|
-
).toHaveText(/Go to About/);
|
|
31
|
-
|
|
32
|
-
expect(errors, `console errors:\n${errors.join('\n')}`).toEqual([]);
|
|
33
|
-
});
|
|
34
|
-
});
|