@lilaquadrat/design-core 0.1.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 +236 -0
- package/html/index.html +25 -0
- package/html/index.mail.html +15 -0
- package/html/index.server.html +29 -0
- package/package.json +148 -0
- package/src/client-entry.ts +28 -0
- package/src/components/partials/action.partial.vue +30 -0
- package/src/components/partials/client-only.partial.vue +11 -0
- package/src/components/partials/error.partial.vue +73 -0
- package/src/components/partials/main-components.partial.vue +167 -0
- package/src/components/partials/mediadetection.partial.vue +71 -0
- package/src/components/partials/qrcode.partial.vue +35 -0
- package/src/customs.ts +12 -0
- package/src/env.d.ts +1 -0
- package/src/functions/PaymenyProviderFactory.ts +27 -0
- package/src/functions/lila-dom.ts +100 -0
- package/src/functions/shopify.provider.ts +378 -0
- package/src/functions/stripe.provider.ts +181 -0
- package/src/globals.d.ts +19 -0
- package/src/index.ts +47 -0
- package/src/interfaces/AppState.interface.ts +4 -0
- package/src/interfaces/EditorConfiguration.interface.ts +15 -0
- package/src/interfaces/EventDeclaration.interface.ts +19 -0
- package/src/interfaces/FrontendConfig.interface.ts +42 -0
- package/src/interfaces/GenericEvents.interface.ts +6 -0
- package/src/interfaces/GenericState.interface.ts +5 -0
- package/src/interfaces/IconsPartial.ts +9 -0
- package/src/interfaces/IdTokenExtended.interface.ts +7 -0
- package/src/interfaces/ModuleBaseProps.interface.ts +8 -0
- package/src/interfaces/PaymentProvider.interface.ts +13 -0
- package/src/libs/ActionNotice.ts +235 -0
- package/src/libs/Models.class.ts +482 -0
- package/src/main.ts +84 -0
- package/src/mixins/createCookieString.ts +78 -0
- package/src/mixins/createModelDeclaration.ts +29 -0
- package/src/mixins/createRouter.ts +30 -0
- package/src/mixins/date.ts +23 -0
- package/src/mixins/formatSize.ts +12 -0
- package/src/mixins/getAnchor.ts +14 -0
- package/src/mixins/getRoutes.ts +50 -0
- package/src/mixins/hasSlotContent.ts +32 -0
- package/src/mixins/hooks.ts +104 -0
- package/src/mixins/loadComponents.ts +107 -0
- package/src/mixins/logger.ts +32 -0
- package/src/mixins/replaceVariables.ts +19 -0
- package/src/mixins/scroll.ts +83 -0
- package/src/models/Address.model.ts +24 -0
- package/src/models/Contact.model.ts +22 -0
- package/src/models.ts +2 -0
- package/src/plugins/auth.ts +121 -0
- package/src/plugins/currency.ts +26 -0
- package/src/plugins/events.ts +156 -0
- package/src/plugins/filters.ts +43 -0
- package/src/plugins/inview.ts +351 -0
- package/src/plugins/replacer.ts +18 -0
- package/src/plugins/resize.ts +128 -0
- package/src/plugins/signupFlow.ts +89 -0
- package/src/plugins/traceable.ts +53 -0
- package/src/plugins/translations.ts +29 -0
- package/src/plugins/youtube.ts +80 -0
- package/src/routes.ts +132 -0
- package/src/server-entry.ts +113 -0
- package/src/stores/calls.store.ts +33 -0
- package/src/stores/cart.store.ts +186 -0
- package/src/stores/content.store.ts +83 -0
- package/src/stores/editor.store.ts +27 -0
- package/src/stores/files.store.ts +166 -0
- package/src/stores/main.store.ts +184 -0
- package/src/stores/user.store.ts +124 -0
- package/src/translations/de.ts +138 -0
- package/src/views/content.view.vue +219 -0
- package/src/views/download.view.vue +143 -0
- package/src/views/editor.view.vue +243 -0
- package/src/views/login.view.vue +82 -0
- package/src/views/signup-account.view.vue +64 -0
- package/tooling/cypress/config.ts +80 -0
- package/tooling/cypress/generate-manifest.js +5 -0
- package/tooling/cypress/manifest-utils.mjs +40 -0
- package/tooling/cypress/run-parallel.mjs +77 -0
- package/tooling/cypress/support/commands.ts +436 -0
- package/tooling/cypress/support/e2e.ts +17 -0
- package/tooling/eslint.config.js +223 -0
- package/tooling/preview.plugin.ts +134 -0
- package/tooling/ssr-test/index.mjs +391 -0
- package/tooling/stylelint.config.mjs +24 -0
- package/tooling/vite.ts +246 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { Plugin, UserConfig } from 'vite';
|
|
2
|
+
|
|
3
|
+
const OUTPUT_DIR = 'dist/preview';
|
|
4
|
+
const PREVIEW_ASSET_DIR = 'previewAssets';
|
|
5
|
+
|
|
6
|
+
function escapeRegExp (input: string) {
|
|
7
|
+
|
|
8
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
9
|
+
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* drops the gallery fixture images from every build that is not the preview.
|
|
14
|
+
*
|
|
15
|
+
* the viewData fixtures import them statically, and vite emits an imported asset
|
|
16
|
+
* while transforming - so even though rolldown tree shakes preview.view.vue out
|
|
17
|
+
* of the app bundle, the images survive as orphans nobody references.
|
|
18
|
+
*/
|
|
19
|
+
export function dropPreviewAssets (): Plugin {
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
name : 'drop-preview-assets',
|
|
23
|
+
enforce: 'post',
|
|
24
|
+
apply : 'build',
|
|
25
|
+
generateBundle (_options, bundle: Record<string, any>) {
|
|
26
|
+
|
|
27
|
+
Object.keys(bundle).forEach((fileName) => {
|
|
28
|
+
|
|
29
|
+
const single = bundle[fileName];
|
|
30
|
+
|
|
31
|
+
if(single.type !== 'asset') return;
|
|
32
|
+
|
|
33
|
+
const origins: string[] = [single.originalFileName, ...single.originalFileNames ?? []].filter(Boolean);
|
|
34
|
+
|
|
35
|
+
if(origins.some((origin) => origin.includes(PREVIEW_ASSET_DIR))) delete bundle[fileName];
|
|
36
|
+
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* build overrides for the shareable static gallery (yarn build:preview).
|
|
46
|
+
*
|
|
47
|
+
* everything here exists because the output is opened straight from disk, where
|
|
48
|
+
* each file is its own opaque origin.
|
|
49
|
+
*/
|
|
50
|
+
export function previewBuildOptions (base: UserConfig['build']): UserConfig['build'] {
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
...base,
|
|
54
|
+
/**
|
|
55
|
+
* inline fonts and images as data URIs: browsers block the font fetches a
|
|
56
|
+
* separate .woff would need
|
|
57
|
+
*/
|
|
58
|
+
assetsInlineLimit: 512 * 1024,
|
|
59
|
+
rolldownOptions : {
|
|
60
|
+
...base?.rolldownOptions,
|
|
61
|
+
output: {
|
|
62
|
+
...base?.rolldownOptions?.output,
|
|
63
|
+
// a single classic script: file:// blocks `type="module"` as cross origin
|
|
64
|
+
dir : OUTPUT_DIR,
|
|
65
|
+
format : 'iife',
|
|
66
|
+
codeSplitting : false,
|
|
67
|
+
entryFileNames: '[name].js',
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* folds the script and the stylesheet into index.html, so the preview is a self
|
|
76
|
+
* contained file that opens over file:// with no server.
|
|
77
|
+
*/
|
|
78
|
+
export default function previewBundle (): Plugin {
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
name : 'preview-bundle',
|
|
82
|
+
enforce: 'post',
|
|
83
|
+
apply : 'build',
|
|
84
|
+
generateBundle (_options, bundle: Record<string, any>) {
|
|
85
|
+
|
|
86
|
+
const html = bundle['index.html'];
|
|
87
|
+
|
|
88
|
+
if(!html) return;
|
|
89
|
+
|
|
90
|
+
let source = html.source.toString();
|
|
91
|
+
let script = '';
|
|
92
|
+
|
|
93
|
+
Object.keys(bundle).forEach((fileName) => {
|
|
94
|
+
|
|
95
|
+
const single = bundle[fileName];
|
|
96
|
+
|
|
97
|
+
if(fileName === 'index.html') return;
|
|
98
|
+
|
|
99
|
+
if(single.type === 'chunk') {
|
|
100
|
+
|
|
101
|
+
// a literal </script> inside the code would end the tag early
|
|
102
|
+
script += single.code.replace(/<\/script>/gi, '<\\/script>');
|
|
103
|
+
source = source.replace(new RegExp(`<script[^>]*src="[^"]*${escapeRegExp(fileName)}"[^>]*></script>\\s*`), '');
|
|
104
|
+
delete bundle[fileName];
|
|
105
|
+
return;
|
|
106
|
+
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if(fileName.endsWith('.css')) {
|
|
110
|
+
|
|
111
|
+
// replacer functions only: a replacement string would expand $& and
|
|
112
|
+
// $\` in the asset as substitution patterns
|
|
113
|
+
source = source.replace(new RegExp(`<link[^>]*href="[^"]*${escapeRegExp(fileName)}"[^>]*>`), () => `<style>${single.source}</style>`);
|
|
114
|
+
delete bundle[fileName];
|
|
115
|
+
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
source = source.replace(/<link[^>]*rel="modulepreload"[^>]*>\s*/g, '');
|
|
121
|
+
// the manifest is the one head asset a file:// origin cannot fetch
|
|
122
|
+
source = source.replace(/<link[^>]*rel="manifest"[^>]*>\s*/g, '');
|
|
123
|
+
Object.keys(bundle).filter((fileName) => fileName.endsWith('.webmanifest')).forEach((fileName) => delete bundle[fileName]);
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* at the end of the body, not in place: an inline script has no `defer`,
|
|
127
|
+
* so in the head it would run before #app exists
|
|
128
|
+
*/
|
|
129
|
+
html.source = source.replace('</body>', () => `<script>${script}</script>\n</body>`);
|
|
130
|
+
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSR preflight for a design.
|
|
3
|
+
*
|
|
4
|
+
* Renders every page a design can produce through the real ssr build - the same
|
|
5
|
+
* dist/server/server-entry.js that gets published - and fails if anything in it
|
|
6
|
+
* throws, warns, or comes back empty.
|
|
7
|
+
*
|
|
8
|
+
* It exists because the production renderer is forgiving in the worst way:
|
|
9
|
+
* RenderClass.renderSingle collects the renders with Promise.allSettled and only
|
|
10
|
+
* console.errors a rejected one, so a page that blows up during SSR is silently
|
|
11
|
+
* published as an empty shell. This suite is the gate before that happens.
|
|
12
|
+
*
|
|
13
|
+
* Offline by design: everything comes from the build output and the fixtures
|
|
14
|
+
* bundle, nothing talks to an api.
|
|
15
|
+
*/
|
|
16
|
+
import { readFile } from 'node:fs/promises';
|
|
17
|
+
import { resolve } from 'node:path';
|
|
18
|
+
import { describe, it } from 'node:test';
|
|
19
|
+
import assert from 'node:assert/strict';
|
|
20
|
+
|
|
21
|
+
const DEFAULT_PATHS = {
|
|
22
|
+
serverEntry : 'dist/server/server-entry.js',
|
|
23
|
+
manifest : 'dist/server/.vite/ssr-manifest.json',
|
|
24
|
+
template : 'dist/server/index.server.html',
|
|
25
|
+
mailTemplate: 'dist/server/index.mail.html',
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* the app logs its startup through mixins/logger, which is console.log - only
|
|
29
|
+
* warnings and errors are interesting here
|
|
30
|
+
*/
|
|
31
|
+
const VUE_WARN = '[Vue warn]';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* mirrors RenderClass.createGenericContext: the shape the STUDIO renderer hands
|
|
35
|
+
* to render(), minus the parts that only matter for asset urls.
|
|
36
|
+
*/
|
|
37
|
+
function createContext (content, options = {}) {
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
data : content,
|
|
41
|
+
layout : options.layout,
|
|
42
|
+
settings : {
|
|
43
|
+
name : content.company ? `${content.company}-${content.project}` : 'preflight',
|
|
44
|
+
company: content.company ?? 'preflight',
|
|
45
|
+
project: content.project ?? 'preflight',
|
|
46
|
+
mode : content.settings?.mode ?? 'presentation',
|
|
47
|
+
},
|
|
48
|
+
tags : content.tags,
|
|
49
|
+
cdn : '',
|
|
50
|
+
renderTarget: options.renderTarget ?? 'web',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* turns a fixture into a site the renderer can address: a filename to look up
|
|
57
|
+
* and a url that resolves to it, exactly as Publish.action stores one.
|
|
58
|
+
*/
|
|
59
|
+
function createSite (name, content, options = {}) {
|
|
60
|
+
const target = options.target ?? content.target;
|
|
61
|
+
const filename = target === 'mail'
|
|
62
|
+
? ['mail']
|
|
63
|
+
: content.settings?.filename ?? [name];
|
|
64
|
+
const site = {
|
|
65
|
+
...content,
|
|
66
|
+
id : content.id ?? name,
|
|
67
|
+
state : content.state ?? 'publish',
|
|
68
|
+
settings: {
|
|
69
|
+
mode : 'presentation',
|
|
70
|
+
title: name,
|
|
71
|
+
...content.settings,
|
|
72
|
+
filename,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (target) site.target = target;
|
|
77
|
+
|
|
78
|
+
return { name, site, url: site.settings.url || filename[0] };
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* vue reports a failed serverPrefetch and an unresolved component through the
|
|
84
|
+
* console rather than by rejecting, so the console is part of the assertion.
|
|
85
|
+
*/
|
|
86
|
+
async function captureConsole (run) {
|
|
87
|
+
|
|
88
|
+
const captured = { errors: [], warnings: [] };
|
|
89
|
+
const originalError = console.error;
|
|
90
|
+
const originalWarn = console.warn;
|
|
91
|
+
|
|
92
|
+
console.error = (...args) => captured.errors.push(args.map(stringify).join(' '));
|
|
93
|
+
console.warn = (...args) => captured.warnings.push(args.map(stringify).join(' '));
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
|
|
97
|
+
const result = await run();
|
|
98
|
+
|
|
99
|
+
return { result, captured };
|
|
100
|
+
|
|
101
|
+
} finally {
|
|
102
|
+
|
|
103
|
+
console.error = originalError;
|
|
104
|
+
console.warn = originalWarn;
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function stringify (value) {
|
|
111
|
+
|
|
112
|
+
if (value instanceof Error) return `${value.message}\n${value.stack}`;
|
|
113
|
+
if (typeof value === 'string') return value;
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
|
|
117
|
+
return JSON.stringify(value);
|
|
118
|
+
|
|
119
|
+
} catch {
|
|
120
|
+
|
|
121
|
+
return String(value);
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function assembleDocument (template, rendered) {
|
|
128
|
+
|
|
129
|
+
return template
|
|
130
|
+
.replace('<!--preload-links-->', rendered.preloadLinks)
|
|
131
|
+
.replace('<!--ssr-outlet-->', rendered.html)
|
|
132
|
+
.replace('<!--pinia-state-->', `<script>window.__INITIAL_STATE__ = ${rendered.initialState}</script>`);
|
|
133
|
+
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* every marker a module could plausibly put on its root element. the class is a
|
|
138
|
+
* convention, not a contract - a handful of modules render no wrapper at all -
|
|
139
|
+
* so a miss is reported, never asserted.
|
|
140
|
+
*/
|
|
141
|
+
function moduleMarkers (type, overrides = {}) {
|
|
142
|
+
|
|
143
|
+
if (overrides[type]) return overrides[type];
|
|
144
|
+
|
|
145
|
+
const base = type.replace(/-module$/, '');
|
|
146
|
+
|
|
147
|
+
return [`lila-${base}-module`, `${base}-module`, `lila-${base}`];
|
|
148
|
+
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* splits captured console output into what the suite must fail on and what the
|
|
153
|
+
* project has declared as expected - a page that needs data only the backend
|
|
154
|
+
* has (a structure declaration, a payment provider) complains offline, and that
|
|
155
|
+
* complaint is not a regression.
|
|
156
|
+
*/
|
|
157
|
+
function splitAllowed (messages, allowed) {
|
|
158
|
+
|
|
159
|
+
const unexpected = [];
|
|
160
|
+
const expected = [];
|
|
161
|
+
|
|
162
|
+
messages.forEach((single) => {
|
|
163
|
+
|
|
164
|
+
if (allowed.some((pattern) => pattern.test(single))) {
|
|
165
|
+
|
|
166
|
+
expected.push(single);
|
|
167
|
+
|
|
168
|
+
} else {
|
|
169
|
+
|
|
170
|
+
unexpected.push(single);
|
|
171
|
+
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return { unexpected, expected };
|
|
177
|
+
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function assertRendered (name, rendered, captured, options = {}) {
|
|
181
|
+
|
|
182
|
+
const allowed = options.allowedConsole ?? [];
|
|
183
|
+
const errors = splitAllowed(captured.errors, allowed);
|
|
184
|
+
const warnings = splitAllowed(
|
|
185
|
+
captured.warnings.filter((single) => single.includes(VUE_WARN)),
|
|
186
|
+
allowed,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
assert.ok(rendered, `${name}: render returned nothing`);
|
|
190
|
+
assert.equal(
|
|
191
|
+
errors.unexpected.length,
|
|
192
|
+
0,
|
|
193
|
+
`${name}: console.error during render\n${errors.unexpected.join('\n')}`,
|
|
194
|
+
);
|
|
195
|
+
assert.equal(
|
|
196
|
+
warnings.unexpected.length,
|
|
197
|
+
0,
|
|
198
|
+
`${name}: vue warnings during render\n${warnings.unexpected.join('\n')}`,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (options.onExpected && (errors.expected.length || warnings.expected.length)) {
|
|
202
|
+
|
|
203
|
+
options.onExpected([...errors.expected, ...warnings.expected]);
|
|
204
|
+
|
|
205
|
+
}
|
|
206
|
+
assert.equal(typeof rendered.html, 'string', `${name}: html is not a string`);
|
|
207
|
+
|
|
208
|
+
const contentMarker = options.contentMarker ?? 'lila-content-module';
|
|
209
|
+
|
|
210
|
+
if (contentMarker) {
|
|
211
|
+
|
|
212
|
+
assert.ok(
|
|
213
|
+
rendered.html.includes(contentMarker),
|
|
214
|
+
`${name}: ${contentMarker} did not render - the page would publish as an empty shell`,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
assert.doesNotThrow(
|
|
220
|
+
() => JSON.parse(rendered.initialState),
|
|
221
|
+
`${name}: initialState is not valid json, hydration would fail`,
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function loadRenderer (root, paths) {
|
|
227
|
+
|
|
228
|
+
const serverEntry = resolve(root, paths.serverEntry);
|
|
229
|
+
const renderer = await import(serverEntry);
|
|
230
|
+
|
|
231
|
+
assert.equal(typeof renderer.render, 'function', `${paths.serverEntry} does not export render()`);
|
|
232
|
+
|
|
233
|
+
return renderer;
|
|
234
|
+
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function loadJson (root, path) {
|
|
238
|
+
|
|
239
|
+
return JSON.parse(await readFile(resolve(root, path), 'utf-8'));
|
|
240
|
+
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* registers the whole preflight against node:test.
|
|
245
|
+
*
|
|
246
|
+
* @param {object} options
|
|
247
|
+
* @param {string} [options.root] project root, defaults to process.cwd()
|
|
248
|
+
* @param {object} options.fixtures the built fixtures bundle: { viewData, modules, mailModules }
|
|
249
|
+
* @param {object} [options.paths] overrides for the build output locations
|
|
250
|
+
* @param {boolean} [options.variants] also render every registered variant on its own, default true
|
|
251
|
+
* @param {RegExp[]} [options.allowedConsole] console output the project expects offline, reported instead of failed
|
|
252
|
+
* @param {Record<string, string[]>} [options.markers] root markers for modules whose class does not follow from their type
|
|
253
|
+
*/
|
|
254
|
+
export async function runSsrSuite (options) {
|
|
255
|
+
|
|
256
|
+
const root = options.root ?? process.cwd();
|
|
257
|
+
const paths = { ...DEFAULT_PATHS, ...options.paths };
|
|
258
|
+
const fixtures = options.fixtures;
|
|
259
|
+
const withVariants = options.variants !== false;
|
|
260
|
+
const allowedConsole = options.allowedConsole ?? [];
|
|
261
|
+
const markers = options.markers ?? {};
|
|
262
|
+
const renderer = await loadRenderer(root, paths);
|
|
263
|
+
const manifest = await loadJson(root, paths.manifest);
|
|
264
|
+
const template = await readFile(resolve(root, paths.template), 'utf-8');
|
|
265
|
+
|
|
266
|
+
async function render (entry, renderOptions = {}) {
|
|
267
|
+
|
|
268
|
+
return captureConsole(() => renderer.render(
|
|
269
|
+
entry.url,
|
|
270
|
+
createContext(entry.site, renderOptions),
|
|
271
|
+
renderOptions.contextData ?? null,
|
|
272
|
+
renderOptions.recipient ?? null,
|
|
273
|
+
renderOptions.list ?? null,
|
|
274
|
+
manifest,
|
|
275
|
+
{ cdnUrl: '' },
|
|
276
|
+
));
|
|
277
|
+
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
describe('ssr: viewData renders as its own site', () => {
|
|
281
|
+
|
|
282
|
+
Object.entries(fixtures.viewData ?? {}).forEach(([name, content]) => {
|
|
283
|
+
|
|
284
|
+
it(name, async (t) => {
|
|
285
|
+
|
|
286
|
+
if (!Array.isArray(content?.modules)) {
|
|
287
|
+
|
|
288
|
+
t.diagnostic(`${name} is not page content (no modules array) - skipped`);
|
|
289
|
+
return;
|
|
290
|
+
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const entry = createSite(name, content);
|
|
294
|
+
const { result, captured } = await render(entry);
|
|
295
|
+
|
|
296
|
+
assertRendered(name, result, captured, {
|
|
297
|
+
allowedConsole,
|
|
298
|
+
onExpected: (messages) => t.diagnostic(`${name}: expected offline complaints\n${messages.join('\n')}`),
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const document = assembleDocument(template, result);
|
|
302
|
+
|
|
303
|
+
assert.ok(
|
|
304
|
+
!document.includes('<!--ssr-outlet-->'),
|
|
305
|
+
`${name}: the html template still holds its placeholder`,
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
content.modules.forEach((single) => {
|
|
309
|
+
|
|
310
|
+
if (!single?.type) return;
|
|
311
|
+
|
|
312
|
+
const found = moduleMarkers(single.type, markers).some((marker) => result.html.includes(marker));
|
|
313
|
+
|
|
314
|
+
if (!found) t.diagnostic(`${name}: no root marker found for ${single.type}`);
|
|
315
|
+
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
describe('ssr: every module renders with no data at all', () => {
|
|
325
|
+
|
|
326
|
+
(fixtures.modules ?? []).forEach((single) => {
|
|
327
|
+
|
|
328
|
+
const type = `${single.name}-module`;
|
|
329
|
+
|
|
330
|
+
it(type, async () => {
|
|
331
|
+
|
|
332
|
+
const entry = createSite(`empty-${single.name}`, {
|
|
333
|
+
modules: [{ type, uuid: `empty-${single.name}` }],
|
|
334
|
+
});
|
|
335
|
+
const { result, captured } = await render(entry);
|
|
336
|
+
|
|
337
|
+
assertRendered(type, result, captured, { allowedConsole });
|
|
338
|
+
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
if (!withVariants) return;
|
|
342
|
+
|
|
343
|
+
(single.variants ?? []).forEach((variant) => {
|
|
344
|
+
|
|
345
|
+
if (!variant?.key) return;
|
|
346
|
+
|
|
347
|
+
it(`${type} [${variant.key}]`, async () => {
|
|
348
|
+
|
|
349
|
+
const entry = createSite(`empty-${single.name}-${variant.key}`, {
|
|
350
|
+
modules: [{ type, uuid: `empty-${single.name}-${variant.key}`, variant: [variant.key] }],
|
|
351
|
+
});
|
|
352
|
+
const { result, captured } = await render(entry);
|
|
353
|
+
|
|
354
|
+
assertRendered(`${type} [${variant.key}]`, result, captured, { allowedConsole });
|
|
355
|
+
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
if (!fixtures.mailModules?.length) return;
|
|
365
|
+
|
|
366
|
+
describe('ssr: every mail module renders with no data at all', () => {
|
|
367
|
+
|
|
368
|
+
fixtures.mailModules.forEach((single) => {
|
|
369
|
+
|
|
370
|
+
const type = `${single.name}-module`;
|
|
371
|
+
|
|
372
|
+
it(type, async () => {
|
|
373
|
+
|
|
374
|
+
const entry = createSite(`empty-mail-${single.name}`, {
|
|
375
|
+
target : 'mail',
|
|
376
|
+
modules: [{ type, uuid: `empty-mail-${single.name}` }],
|
|
377
|
+
}, { target: 'mail' });
|
|
378
|
+
const { result, captured } = await render(entry);
|
|
379
|
+
|
|
380
|
+
assertRendered(type, result, captured, { allowedConsole });
|
|
381
|
+
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export default runSsrSuite;
|
|
391
|
+
export { createSite, createContext, assembleDocument, captureConsole };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** @type {import('stylelint').Config} */
|
|
2
|
+
export default {
|
|
3
|
+
'extends': [
|
|
4
|
+
'stylelint-config-html',
|
|
5
|
+
'stylelint-config-recommended-vue'
|
|
6
|
+
],
|
|
7
|
+
'plugins': [
|
|
8
|
+
'stylelint-order'
|
|
9
|
+
],
|
|
10
|
+
'overrides': [
|
|
11
|
+
{
|
|
12
|
+
'files' : ['*.vue', '**/*.vue'],
|
|
13
|
+
'customSyntax': 'postcss-html'
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
'files' : ['*.less', '**/*.less'],
|
|
17
|
+
'customSyntax': 'postcss-less'
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
'rules': {
|
|
21
|
+
'media-query-no-invalid' : null,
|
|
22
|
+
'declaration-property-value-no-unknown': null
|
|
23
|
+
}
|
|
24
|
+
}
|