apostrophe 3.16.0 → 3.17.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.
Files changed (39) hide show
  1. package/.eslintignore +3 -1
  2. package/CHANGELOG.md +23 -1
  3. package/index.js +5 -4
  4. package/lib/moog.js +14 -1
  5. package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +3 -1
  6. package/modules/@apostrophecms/asset/index.js +213 -101
  7. package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.config.js +3 -3
  8. package/modules/@apostrophecms/asset/lib/webpack/src/webpack.config.js +30 -12
  9. package/modules/@apostrophecms/asset/lib/webpack/src/webpack.es5.js +1 -1
  10. package/modules/@apostrophecms/asset/lib/webpack/src/webpack.scss.js +6 -2
  11. package/modules/@apostrophecms/asset/lib/webpack/utils.js +266 -0
  12. package/modules/@apostrophecms/asset/views/scripts.html +1 -0
  13. package/modules/@apostrophecms/asset/views/stylesheets.html +1 -0
  14. package/modules/@apostrophecms/doc/index.js +64 -0
  15. package/modules/@apostrophecms/doc-type/index.js +35 -0
  16. package/modules/@apostrophecms/i18n/i18n/en.json +2 -0
  17. package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +7 -0
  18. package/modules/@apostrophecms/login/index.js +4 -0
  19. package/modules/@apostrophecms/schema/index.js +40 -49
  20. package/modules/@apostrophecms/schema/ui/apos/components/AposInputObject.vue +67 -0
  21. package/modules/@apostrophecms/template/index.js +14 -5
  22. package/modules/@apostrophecms/template/lib/bundlesLoader.js +158 -0
  23. package/modules/@apostrophecms/template/views/outerLayoutBase.html +6 -0
  24. package/modules/@apostrophecms/widget-type/index.js +21 -0
  25. package/package.json +1 -1
  26. package/test/areas.js +2 -1
  27. package/test/assets.js +307 -3
  28. package/test/docs.js +67 -2
  29. package/test/modules/bundle/index.js +7 -0
  30. package/test/modules/bundle-page/index.js +32 -0
  31. package/test/modules/bundle-page/ui/src/extra.js +1 -0
  32. package/test/modules/bundle-page/ui/src/extra.scss +0 -0
  33. package/test/modules/bundle-page/views/index.html +9 -0
  34. package/test/modules/bundle-page/views/show.html +10 -0
  35. package/test/modules/bundle-widget/index.js +27 -0
  36. package/test/modules/bundle-widget/ui/src/extra2.js +1 -0
  37. package/test/modules/bundle-widget/views/widget.html +1 -0
  38. package/test/pieces.js +38 -12
  39. package/test/public/static-test.txt +0 -1
@@ -0,0 +1,67 @@
1
+ <template>
2
+ <!-- Errors for the entire object field are not interesting,
3
+ let the relevant subfield's error shine on its own -->
4
+ <AposInputWrapper
5
+ :field="field"
6
+ :error="null"
7
+ :uid="uid"
8
+ :display-options="displayOptions"
9
+ >
10
+ <template #body>
11
+ <div class="apos-input-object">
12
+ <div class="apos-input-wrapper">
13
+ <AposSchema
14
+ :schema="field.schema"
15
+ :trigger-validation="triggerValidation"
16
+ :utility-rail="false"
17
+ v-model="schemaInput"
18
+ ref="schema"
19
+ />
20
+ </div>
21
+ </div>
22
+ </template>
23
+ </AposInputWrapper>
24
+ </template>
25
+
26
+ <script>
27
+ import AposInputMixin from 'Modules/@apostrophecms/schema/mixins/AposInputMixin.js';
28
+ import { klona } from 'klona';
29
+
30
+ export default {
31
+ name: 'AposInputObject',
32
+ mixins: [ AposInputMixin ],
33
+ data () {
34
+ const next = this.value ? this.value.data : (this.field.def || {});
35
+ return {
36
+ schemaInput: {
37
+ data: next
38
+ },
39
+ next
40
+ };
41
+ },
42
+ watch: {
43
+ schemaInput() {
44
+ this.next = this.schemaInput.data;
45
+ }
46
+ },
47
+ methods: {
48
+ validate (value) {
49
+ if (this.schemaInput.hasErrors) {
50
+ return 'invalid';
51
+ }
52
+ }
53
+ }
54
+ };
55
+ </script>
56
+
57
+ <style scoped>
58
+ .apos-input-object {
59
+ border-left: 1px solid var(--a-base-9);
60
+ }
61
+ .apos-input-wrapper {
62
+ margin: 20px 0 0 19px;
63
+ }
64
+ .apos-input-object ::v-deep .apos-schema .apos-field {
65
+ margin-bottom: 30px;
66
+ }
67
+ </style>
@@ -119,6 +119,7 @@ module.exports = {
119
119
  },
120
120
  methods(self) {
121
121
  return {
122
+ ...require('./lib/bundlesLoader')(self),
122
123
 
123
124
  // Add helpers in the namespace for a particular module.
124
125
  // They will be visible in nunjucks at
@@ -630,8 +631,6 @@ module.exports = {
630
631
  // async function.
631
632
 
632
633
  async renderPageForModule(req, template, data, module) {
633
-
634
- let content;
635
634
  let scene = req.user ? 'apos' : 'public';
636
635
  if (req.scene) {
637
636
  scene = req.scene;
@@ -699,15 +698,25 @@ module.exports = {
699
698
  }
700
699
 
701
700
  try {
702
- content = await module.render(req, template, args);
701
+ const content = await module.render(req, template, args);
702
+
703
+ const filledContent = self.insertBundlesMarkup({
704
+ page: req.data.bestPage,
705
+ scene,
706
+ template,
707
+ content,
708
+ scriptsPlaceholder: req.scriptsPlaceholder,
709
+ stylesheetsPlaceholder: req.stylesheetsPlaceholder,
710
+ widgetsBundles: req.widgetsBundles
711
+ });
712
+
713
+ return filledContent;
703
714
  } catch (e) {
704
715
  // The page template threw an exception. Log where it
705
716
  // occurred for easier debugging
706
717
  return error(e);
707
718
  }
708
719
 
709
- return content;
710
-
711
720
  function error(e) {
712
721
  self.logError(req, e);
713
722
  req.statusCode = 500;
@@ -0,0 +1,158 @@
1
+ const { stripIndent } = require('common-tags');
2
+
3
+ module.exports = (self) => {
4
+ function insertBundlesMarkup({
5
+ page = {},
6
+ template = '',
7
+ scene,
8
+ content,
9
+ scriptsPlaceholder,
10
+ stylesheetsPlaceholder,
11
+ widgetsBundles = {}
12
+ }) {
13
+ const renderMarkup = renderBundleMarkup(
14
+ self.apos.template.safe,
15
+ self.apos.asset.getAssetBaseUrl()
16
+ );
17
+
18
+ if (!scriptsPlaceholder && !stylesheetsPlaceholder) {
19
+ return content;
20
+ }
21
+
22
+ const { es5 } = self.apos.asset.options;
23
+ const { extraBundles } = self.apos.asset;
24
+
25
+ const jsMainBundle = renderMarkup({
26
+ fileName: scene,
27
+ ext: 'js',
28
+ es5
29
+ });
30
+ const cssMainBundle = renderMarkup({
31
+ fileName: scene,
32
+ ext: 'css'
33
+ });
34
+
35
+ if (scene === 'apos') {
36
+ return loadAllBundles({
37
+ content,
38
+ scriptsPlaceholder,
39
+ stylesheetsPlaceholder,
40
+ extraBundles,
41
+ renderMarkup,
42
+ jsMainBundle,
43
+ cssMainBundle,
44
+ es5
45
+ });
46
+ }
47
+
48
+ const templateType = template.substring(template.lastIndexOf(':') + 1);
49
+ const pageModule = page.type && self.apos.modules[page.type];
50
+ const { webpack = {} } = pageModule ? pageModule.__meta : {};
51
+
52
+ const configs = Object.values(webpack || {}).reduce((acc, config) => ({
53
+ ...acc,
54
+ ...config && config.bundles
55
+ }), widgetsBundles);
56
+
57
+ const { jsBundles, cssBundles } = Object.entries(configs)
58
+ .reduce((acc, [ name, { templates } ]) => {
59
+ if (templates && !templates.includes(templateType)) {
60
+ return acc;
61
+ }
62
+
63
+ const jsMarkup = scriptsPlaceholder &&
64
+ extraBundles.js.includes(name) &&
65
+ renderMarkup({
66
+ fileName: name,
67
+ ext: 'js',
68
+ es5
69
+ });
70
+
71
+ const cssMarkup = stylesheetsPlaceholder &&
72
+ extraBundles.css.includes(name) &&
73
+ renderMarkup({
74
+ fileName: name,
75
+ ext: 'css'
76
+ });
77
+
78
+ return {
79
+ jsBundles: stripIndent`
80
+ ${acc.jsBundles}
81
+ ${jsMarkup || ''}
82
+ `,
83
+ cssBundles: stripIndent`
84
+ ${acc.cssBundles}
85
+ ${cssMarkup || ''}
86
+ `
87
+ };
88
+ }, {
89
+ jsBundles: jsMainBundle,
90
+ cssBundles: cssMainBundle
91
+ });
92
+
93
+ return content
94
+ .replace(scriptsPlaceholder, jsBundles)
95
+ .replace(stylesheetsPlaceholder, cssBundles);
96
+ }
97
+
98
+ return { insertBundlesMarkup };
99
+ };
100
+
101
+ function renderBundleMarkup (safe, base) {
102
+ return ({
103
+ fileName, ext = 'js', es5 = false
104
+ }) => {
105
+ if (ext === 'css') {
106
+ return safe(stripIndent`
107
+ <link href="${base}/${fileName}-bundle.css" rel="stylesheet" />
108
+ `);
109
+ }
110
+
111
+ if (es5) {
112
+ return safe(stripIndent`
113
+ <script nomodule src="${base}/${fileName}-nomodule-bundle.${ext}"></script>
114
+ <script type="module" src="${base}/${fileName}-module-bundle.${ext}"></script>
115
+ `);
116
+ }
117
+
118
+ return safe(stripIndent`
119
+ <script src="${base}/${fileName}-module-bundle.${ext}"></script>
120
+ `);
121
+ };
122
+ }
123
+
124
+ function loadAllBundles({
125
+ content,
126
+ extraBundles,
127
+ scriptsPlaceholder,
128
+ stylesheetsPlaceholder,
129
+ jsMainBundle,
130
+ cssMainBundle,
131
+ renderMarkup,
132
+ es5
133
+ }) {
134
+ const reduceToMarkup = (acc, bundle, ext) => {
135
+ const bundleMarkup = renderMarkup({
136
+ fileName: bundle.replace(`.${ext}`, ''),
137
+ ext,
138
+ es5
139
+ });
140
+
141
+ return stripIndent`
142
+ ${acc}
143
+ ${bundleMarkup}
144
+ `;
145
+ };
146
+
147
+ const jsBundles = extraBundles.js.reduce(
148
+ (acc, bundle) => reduceToMarkup(acc, bundle, 'js'), jsMainBundle
149
+ );
150
+
151
+ const cssBundles = extraBundles.css.reduce(
152
+ (acc, bundle) => reduceToMarkup(acc, bundle, 'css'), cssMainBundle
153
+ );
154
+
155
+ return content
156
+ .replace(scriptsPlaceholder, jsBundles)
157
+ .replace(stylesheetsPlaceholder, cssBundles);
158
+ }
@@ -5,7 +5,10 @@
5
5
  {% endblock %}
6
6
  {% component '@apostrophecms/template:inject' with { where: 'head', end: 'prepend' } %}
7
7
  <title>{% block title %}{{ data.piece.title or data.page.title }}{% endblock %}</title>
8
+
9
+ {# This call is still here for backwards compatibility, but does nothing #}
8
10
  {{ apos.asset.stylesheets(data.scene) }}
11
+
9
12
  {% block standardHead %}
10
13
  <meta name="viewport" content="width=device-width, initial-scale=1">
11
14
  {% endblock %}
@@ -40,7 +43,10 @@
40
43
  <div id="apos-modals"></div>
41
44
  {% endif %}
42
45
  {# Scripts must load after apos-modal in the DOM #}
46
+
47
+ {# This call is still here for backwards compatibility, but does nothing #}
43
48
  {{ apos.asset.scripts(data.scene) }}
49
+
44
50
  {# For project-level webpack injection in dev environments #}
45
51
  {% block afterAposScripts %}{% endblock %}
46
52
  {# Automatically does nothing in production #}
@@ -178,6 +178,11 @@ module.exports = {
178
178
  // async, as are all functions that invoke a nunjucks render in
179
179
  // Apostrophe 3.x.
180
180
  async output(req, widget, options, _with) {
181
+ req.widgetsBundles = {
182
+ ...req.widgetsBundles || {},
183
+ ...self.getWidgetsBundles(`${widget.type}-widget`)
184
+ };
185
+
181
186
  return self.render(req, self.template, {
182
187
  widget: widget,
183
188
  options: options,
@@ -186,6 +191,22 @@ module.exports = {
186
191
  });
187
192
  },
188
193
 
194
+ getWidgetsBundles (widgetType) {
195
+ const widget = self.apos.modules[widgetType];
196
+
197
+ if (!widget) {
198
+ return {};
199
+ }
200
+
201
+ return Object.values(widget.__meta.webpack || {})
202
+ .reduce((acc, config) => {
203
+ return {
204
+ ...acc,
205
+ ...config && config.bundles
206
+ };
207
+ }, {});
208
+ },
209
+
189
210
  // Load relationships and carry out any other necessary async
190
211
  // actions for our type of widget, as long as it is
191
212
  // not forbidden due to the `neverLoad` option or the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apostrophe",
3
- "version": "3.16.0",
3
+ "version": "3.17.0",
4
4
  "description": "The Apostrophe Content Management System.",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/test/areas.js CHANGED
@@ -232,7 +232,8 @@ describe('Areas', function() {
232
232
  assert(doc.main._rendered);
233
233
  assert(!doc.main.items);
234
234
 
235
- // TEMP Commenting out until we add the array item metatype.
235
+ // TODO the approach in this test can't cover array or object area rendering
236
+ // properly without a further overhaul (not a new problem).
236
237
  // if (doc.moreAreas) {
237
238
  // doc.moreAreas.forEach(area => {
238
239
  // assert(area.someWidgets._rendered);
package/test/assets.js CHANGED
@@ -1,18 +1,101 @@
1
1
  const t = require('../test-lib/test.js');
2
2
  const assert = require('assert');
3
+ const fs = require('fs-extra');
4
+ const path = require('path');
5
+
6
+ const {
7
+ checkModulesWebpackConfig,
8
+ getWebpackExtensions,
9
+ fillExtraBundles
10
+ } = require('../modules/@apostrophecms/asset/lib/webpack/utils');
11
+
3
12
  let apos;
4
13
 
14
+ const badModules = {
15
+ badModuleConfig: {
16
+ webpack: {
17
+ badprop: {}
18
+ }
19
+ },
20
+ badModuleConfig2: {
21
+ webpack: []
22
+ }
23
+ };
24
+
25
+ const pagesToInsert = (homeId) => ([
26
+ {
27
+ _id: 'parent:en:published',
28
+ aposLocale: 'en:published',
29
+ metaType: 'doc',
30
+ aposDocId: 'parent',
31
+ type: 'bundle-page',
32
+ slug: '/bundle',
33
+ visibility: 'public',
34
+ path: `${homeId.replace(':en:published', '')}/bundle`,
35
+ level: 1,
36
+ rank: 0,
37
+ main: {
38
+ _id: 'areaId',
39
+ metaType: 'area',
40
+ items: [
41
+ {
42
+ _id: 'widgetId',
43
+ metaType: 'widget',
44
+ type: 'bundle'
45
+ }
46
+ ]
47
+ }
48
+ },
49
+ {
50
+ _id: 'child:en:published',
51
+ title: 'Bundle',
52
+ aposLocale: 'en:published',
53
+ metaType: 'doc',
54
+ aposDocId: 'child',
55
+ type: 'bundle',
56
+ slug: 'child',
57
+ visibility: 'public'
58
+ }
59
+ ]);
60
+
61
+ const modules = {
62
+ '@apostrophecms/page': {
63
+ options: {
64
+ park: [],
65
+ types: [
66
+ {
67
+ name: 'bundle-page',
68
+ label: 'Bundle Page'
69
+ }
70
+ ]
71
+ }
72
+ },
73
+ bundle: {},
74
+ 'bundle-page': {},
75
+ 'bundle-widget': {}
76
+ };
77
+
5
78
  describe('Assets', function() {
79
+ const {
80
+ publicFolderPath,
81
+ getScriptMarkup,
82
+ getStylesheetMarkup,
83
+ expectedBundlesNames,
84
+ deleteBuiltFolders,
85
+ allBundlesAreIncluded
86
+ } = loadUtils();
6
87
 
7
88
  after(async function() {
89
+ await deleteBuiltFolders(publicFolderPath, true);
8
90
  return t.destroy(apos);
9
91
  });
10
92
 
11
- this.timeout(t.timeout);
93
+ this.timeout(90000);
12
94
 
13
- it('should should exist on the apos object', async function() {
95
+ it('should exist on the apos object', async function() {
14
96
  apos = await t.create({
15
- root: module
97
+ root: module,
98
+ modules
16
99
  });
17
100
  assert(apos.asset);
18
101
  });
@@ -21,4 +104,225 @@ describe('Assets', function() {
21
104
  const text = await apos.http.get('/static-test.txt');
22
105
  assert(text.match(/served/));
23
106
  });
107
+
108
+ it('should check that webpack configs in modules are well formatted', async function () {
109
+ const translate = apos.task.getReq().t;
110
+
111
+ assert.doesNotThrow(() => checkModulesWebpackConfig(apos.modules, translate));
112
+
113
+ await t.destroy(apos);
114
+
115
+ apos = await t.create({
116
+ root: module,
117
+ modules: badModules
118
+ });
119
+
120
+ assert.throws(() => checkModulesWebpackConfig(apos.modules, translate));
121
+
122
+ await t.destroy(apos);
123
+ });
124
+
125
+ it('should get webpack extensions from modules and fill extra bundles', async function () {
126
+ const expectedEntryPointsNames = {
127
+ js: [ 'extra', 'extra2' ],
128
+ css: [ 'extra' ]
129
+ };
130
+
131
+ apos = await t.create({
132
+ root: module,
133
+ modules
134
+ });
135
+
136
+ const { extensions, verifiedBundles } = await getWebpackExtensions({
137
+ name: 'src',
138
+ getMetadata: apos.synth.getMetadata,
139
+ modulesToInstantiate: apos.modulesToBeInstantiated()
140
+ });
141
+
142
+ assert(Object.keys(extensions).length === 2);
143
+ assert(!extensions.ext1.resolve.alias.ext1);
144
+ assert(extensions.ext1.resolve.alias.ext1Overriden);
145
+ assert(extensions.ext2.resolve.alias.ext2);
146
+
147
+ assert(Object.keys(verifiedBundles).length === 2);
148
+
149
+ assert(verifiedBundles.extra.js.length === 1);
150
+ assert(verifiedBundles.extra.scss.length === 1);
151
+
152
+ assert(verifiedBundles.extra2.js.length === 1);
153
+ assert(verifiedBundles.extra2.scss.length === 0);
154
+
155
+ const filled = fillExtraBundles(verifiedBundles);
156
+
157
+ filled.js.forEach((name) => {
158
+ assert(expectedEntryPointsNames.js.includes(name));
159
+ });
160
+
161
+ filled.css.forEach((name) => {
162
+ assert(expectedEntryPointsNames.css.includes(name));
163
+ });
164
+ });
165
+
166
+ it('should build the right bundles in dev and prod modes', async function () {
167
+ process.env.NODE_ENV = 'production';
168
+
169
+ await apos.asset.tasks.build.task();
170
+
171
+ const getPath = (p) => `${publicFolderPath}/apos-frontend/` + p;
172
+ const [ releaseId ] = await fs.readdir(getPath('releases'));
173
+
174
+ const checkFileExists = async (p) => fs.pathExists(getPath(p));
175
+ const releasePath = `releases/${releaseId}/default/`;
176
+
177
+ await checkBundlesExists(releasePath, expectedBundlesNames);
178
+ await deleteBuiltFolders(publicFolderPath);
179
+
180
+ process.env.NODE_ENV = 'development';
181
+
182
+ await apos.asset.tasks.build.task();
183
+ await checkBundlesExists('default/', expectedBundlesNames);
184
+
185
+ async function checkBundlesExists (folderPath, fileNames) {
186
+ for (const fileName of fileNames) {
187
+ const extraBundleExists = await checkFileExists(folderPath + fileName);
188
+ assert(extraBundleExists);
189
+ }
190
+ }
191
+ });
192
+
193
+ it('should load the right bundles inside the right page', async function () {
194
+ const { _id: homeId } = await apos.page.find(apos.task.getAnonReq(), { level: 0 }).toObject();
195
+ const jar = apos.http.jar();
196
+
197
+ await apos.doc.db.insertMany(pagesToInsert(homeId));
198
+
199
+ const bundlePage = await apos.http.get('/bundle', { jar });
200
+
201
+ assert(bundlePage.includes(getStylesheetMarkup('public-bundle')));
202
+ assert(!bundlePage.includes(getStylesheetMarkup('extra-bundle')));
203
+
204
+ assert(bundlePage.includes(getScriptMarkup('public-module-bundle')));
205
+ assert(!bundlePage.includes(getScriptMarkup('extra-module-bundle')));
206
+ assert(bundlePage.includes(getScriptMarkup('extra2-module-bundle')));
207
+
208
+ const childPage = await apos.http.get('/bundle/child', { jar });
209
+
210
+ assert(childPage.includes(getStylesheetMarkup('public-bundle')));
211
+ assert(childPage.includes(getStylesheetMarkup('extra-bundle')));
212
+
213
+ assert(childPage.includes(getScriptMarkup('public-module-bundle')));
214
+ assert(childPage.includes(getScriptMarkup('extra-module-bundle')));
215
+ assert(!childPage.includes(getScriptMarkup('extra2-module-bundle')));
216
+ });
217
+
218
+ it('should load all the bundles on all pages when the user is logged in', async function () {
219
+ const user = {
220
+ ...apos.user.newInstance(),
221
+ title: 'toto',
222
+ username: 'toto',
223
+ password: 'tata',
224
+ email: 'toto@mail.com',
225
+ role: 'admin'
226
+ };
227
+
228
+ const jar = apos.http.jar();
229
+
230
+ await apos.user.insert(apos.task.getReq(), user);
231
+
232
+ await apos.http.post(
233
+ '/api/v1/@apostrophecms/login/login',
234
+ {
235
+ method: 'POST',
236
+ body: {
237
+ username: 'toto',
238
+ password: 'tata',
239
+ session: true
240
+ },
241
+ jar
242
+ }
243
+ );
244
+
245
+ const homePage = await apos.http.get('/', { jar });
246
+ assert(homePage.match(/logged in/));
247
+
248
+ const bundlePage = await apos.http.get('/bundle', { jar });
249
+
250
+ allBundlesAreIncluded(bundlePage);
251
+
252
+ await t.destroy(apos);
253
+ });
254
+
255
+ it('should inject modules and nomodules scripts for extra bundles when es5 enabled', async function () {
256
+ const jar = apos.http.jar();
257
+
258
+ const es5Modules = {
259
+ ...modules,
260
+ '@apostrophecms/asset': {
261
+ options: {
262
+ es5: true
263
+ }
264
+ }
265
+ };
266
+
267
+ apos = await t.create({
268
+ root: module,
269
+ modules: es5Modules
270
+ });
271
+
272
+ const { _id: homeId } = await apos.page.find(apos.task.getAnonReq(), { level: 0 }).toObject();
273
+
274
+ await apos.doc.db.insertMany(pagesToInsert(homeId));
275
+
276
+ await apos.asset.tasks.build.task();
277
+
278
+ const bundlePage = await apos.http.get('/bundle', { jar });
279
+
280
+ assert(bundlePage.includes(getScriptMarkup('public-module-bundle', 'module')));
281
+ assert(bundlePage.includes(getScriptMarkup('public-nomodule-bundle', 'nomodule')));
282
+
283
+ assert(bundlePage.includes(getScriptMarkup('extra2-module-bundle', 'module')));
284
+ assert(bundlePage.includes(getScriptMarkup('extra2-nomodule-bundle', 'nomodule')));
285
+ });
24
286
  });
287
+
288
+ function loadUtils () {
289
+ const publicFolderPath = path.join(process.cwd(), 'test/public');
290
+
291
+ const getScriptMarkup = (file, mod) => {
292
+ const moduleStr = mod === 'module' ? ' type="module"' : ' nomodule';
293
+
294
+ return `<script${mod ? moduleStr : ''} src="/apos-frontend/default/${file}.js"></script>`;
295
+ };
296
+
297
+ const getStylesheetMarkup = (file) =>
298
+ `<link href="/apos-frontend/default/${file}.css" rel="stylesheet" />`;
299
+
300
+ const expectedBundlesNames = [ 'extra-module-bundle.js', 'extra2-module-bundle.js', 'extra-bundle.css' ];
301
+
302
+ const deleteBuiltFolders = async (publicPath, deleteAposBuild = false) => {
303
+ await fs.remove(publicPath + '/apos-frontend');
304
+ await fs.remove(publicPath + '/uploads');
305
+
306
+ if (deleteAposBuild) {
307
+ await fs.remove(path.join(process.cwd(), 'test/apos-build'));
308
+ }
309
+ };
310
+
311
+ const allBundlesAreIncluded = (page) => {
312
+ assert(page.includes(getStylesheetMarkup('apos-bundle')));
313
+ assert(page.includes(getStylesheetMarkup('extra-bundle')));
314
+
315
+ assert(page.includes(getScriptMarkup('apos-module-bundle')));
316
+ assert(page.includes(getScriptMarkup('extra-module-bundle')));
317
+ assert(page.includes(getScriptMarkup('extra2-module-bundle')));
318
+ };
319
+
320
+ return {
321
+ publicFolderPath,
322
+ getScriptMarkup,
323
+ getStylesheetMarkup,
324
+ expectedBundlesNames,
325
+ deleteBuiltFolders,
326
+ allBundlesAreIncluded
327
+ };
328
+ }