apostrophe 3.4.1 → 3.8.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/.eslintrc +4 -0
- package/.scratch.md +2 -0
- package/CHANGELOG.md +114 -2
- package/README.md +1 -1
- package/deploy-test-count +1 -1
- package/index.js +125 -5
- package/lib/moog-require.js +41 -3
- package/lib/moog.js +20 -8
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +42 -23
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +30 -14
- package/modules/@apostrophecms/area/index.js +9 -0
- package/modules/@apostrophecms/area/lib/custom-tags/area.js +1 -1
- package/modules/@apostrophecms/area/lib/custom-tags/widget.js +1 -1
- package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +3 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +6 -6
- package/modules/@apostrophecms/asset/index.js +85 -21
- package/modules/@apostrophecms/asset/lib/globalIcons.js +2 -0
- package/modules/@apostrophecms/asset/lib/webpack/src/webpack.scss.js +5 -2
- package/modules/@apostrophecms/attachment/index.js +1 -0
- package/modules/@apostrophecms/db/index.js +5 -6
- package/modules/@apostrophecms/doc/index.js +13 -3
- package/modules/@apostrophecms/doc-type/index.js +24 -4
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +13 -1
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +3 -0
- package/modules/@apostrophecms/i18n/i18n/en.json +26 -6
- package/modules/@apostrophecms/i18n/i18n/es.json +382 -0
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +379 -0
- package/modules/@apostrophecms/i18n/i18n/sk.json +380 -0
- package/modules/@apostrophecms/i18n/index.js +10 -1
- package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +153 -121
- package/modules/@apostrophecms/image/index.js +2 -1
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +6 -3
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +24 -13
- package/modules/@apostrophecms/image-widget/index.js +2 -1
- package/modules/@apostrophecms/image-widget/views/widget.html +12 -2
- package/modules/@apostrophecms/job/index.js +164 -212
- package/modules/@apostrophecms/login/index.js +36 -17
- package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +8 -0
- package/modules/@apostrophecms/migration/index.js +1 -1
- package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +151 -61
- package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +6 -2
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +9 -7
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +12 -15
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +6 -0
- package/modules/@apostrophecms/module/index.js +1 -1
- package/modules/@apostrophecms/notification/index.js +116 -8
- package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +89 -11
- package/modules/@apostrophecms/notification/ui/apos/components/TheAposNotifications.vue +1 -1
- package/modules/@apostrophecms/page/index.js +37 -30
- package/modules/@apostrophecms/permission/index.js +1 -1
- package/modules/@apostrophecms/permission/ui/apos/components/AposInputRole.vue +4 -2
- package/modules/@apostrophecms/piece-type/index.js +178 -61
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +179 -47
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +1 -3
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +138 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +42 -10
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapStyles.vue +3 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Classes.js +6 -10
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Default.js +64 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Document.js +15 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Heading.js +23 -0
- package/modules/@apostrophecms/schema/index.js +97 -20
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +4 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +11 -160
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRadio.vue +8 -5
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +24 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +24 -6
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +0 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +0 -7
- package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +25 -3
- package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +10 -2
- package/modules/@apostrophecms/task/index.js +2 -2
- package/modules/@apostrophecms/template/index.js +63 -36
- package/modules/@apostrophecms/template/lib/custom-tags/component.js +1 -1
- package/modules/@apostrophecms/template/lib/custom-tags/render.js +6 -2
- package/modules/@apostrophecms/ui/index.js +6 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +21 -3
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +205 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue +5 -0
- package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +16 -2
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +4 -3
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +3 -0
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_widgets.scss +3 -0
- package/modules/@apostrophecms/ui/ui/apos/scss/global/import-all.scss +2 -1
- package/modules/@apostrophecms/user/index.js +21 -0
- package/modules/@apostrophecms/util/index.js +2 -2
- package/modules/@apostrophecms/util/ui/src/http.js +12 -8
- package/modules/@apostrophecms/util/ui/src/util.js +15 -0
- package/modules/@apostrophecms/widget-type/index.js +1 -1
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +1 -0
- package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +15 -7
- package/package.json +4 -4
- package/test/extra_node_modules/improve-global/index.js +7 -0
- package/test/extra_node_modules/improve-piece-type/index.js +7 -0
- package/test/improve-overrides.js +30 -0
- package/test/job.js +224 -0
- package/test/login.js +183 -0
- package/test/modules/@apostrophecms/global/index.js +8 -0
- package/test/modules/fragment-all/views/aux-test.html +7 -0
- package/test/modules/fragment-all/views/fragment.html +5 -0
- package/test/moog.js +47 -0
- package/test/package.json +5 -4
- package/test/pieces.js +17 -0
- package/test/reverse-relationship.js +170 -0
- package/test/subdir-project/app.js +3 -0
- package/test/subdir-project.js +26 -0
- package/test/templates.js +7 -1
- package/test-lib/test.js +23 -12
- package/test-lib/util.js +33 -0
package/test/login.js
CHANGED
|
@@ -161,4 +161,187 @@ describe('Login', function() {
|
|
|
161
161
|
assert(page.match(/logged out/));
|
|
162
162
|
});
|
|
163
163
|
|
|
164
|
+
it('Changing a user\'s password should invalidate sessions for that user', async function() {
|
|
165
|
+
|
|
166
|
+
const jar = apos.http.jar();
|
|
167
|
+
|
|
168
|
+
// establish session
|
|
169
|
+
let page = await apos.http.get(
|
|
170
|
+
'/',
|
|
171
|
+
{
|
|
172
|
+
jar
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
assert(page.match(/logged out/));
|
|
177
|
+
|
|
178
|
+
await apos.http.post(
|
|
179
|
+
'/api/v1/@apostrophecms/login/login',
|
|
180
|
+
{
|
|
181
|
+
method: 'POST',
|
|
182
|
+
body: {
|
|
183
|
+
username: 'HarryPutter',
|
|
184
|
+
password: 'crookshanks',
|
|
185
|
+
session: true
|
|
186
|
+
},
|
|
187
|
+
jar
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
page = await apos.http.get(
|
|
192
|
+
'/',
|
|
193
|
+
{
|
|
194
|
+
jar
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
assert(page.match(/logged in/));
|
|
199
|
+
|
|
200
|
+
const req = apos.task.getReq();
|
|
201
|
+
let user = await apos.user.find(req, {
|
|
202
|
+
username: 'HarryPutter'
|
|
203
|
+
}).toObject();
|
|
204
|
+
assert(user);
|
|
205
|
+
user.password = 'VeryPasswordManySecure🐶';
|
|
206
|
+
await apos.user.update(req, user);
|
|
207
|
+
|
|
208
|
+
page = await apos.http.get(
|
|
209
|
+
'/',
|
|
210
|
+
{
|
|
211
|
+
jar
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
assert(!page.match(/logged in/));
|
|
216
|
+
assert(page.match(/logged out/));
|
|
217
|
+
|
|
218
|
+
// Make sure we can come back from that
|
|
219
|
+
await apos.http.post(
|
|
220
|
+
'/api/v1/@apostrophecms/login/login',
|
|
221
|
+
{
|
|
222
|
+
method: 'POST',
|
|
223
|
+
body: {
|
|
224
|
+
username: 'HarryPutter',
|
|
225
|
+
password: 'VeryPasswordManySecure🐶',
|
|
226
|
+
session: true
|
|
227
|
+
},
|
|
228
|
+
jar
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
page = await apos.http.get(
|
|
233
|
+
'/',
|
|
234
|
+
{
|
|
235
|
+
jar
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
assert(page.match(/logged in/));
|
|
240
|
+
|
|
241
|
+
// So we do not have a stale _passwordUpdated flag
|
|
242
|
+
user = await apos.user.find(req, {
|
|
243
|
+
_id: user._id
|
|
244
|
+
}).toObject();
|
|
245
|
+
|
|
246
|
+
// Unrelated writes to user should not invalidate sessions
|
|
247
|
+
user.title = 'Extra Cool Putter';
|
|
248
|
+
await apos.user.update(req, user);
|
|
249
|
+
|
|
250
|
+
page = await apos.http.get(
|
|
251
|
+
'/',
|
|
252
|
+
{
|
|
253
|
+
jar
|
|
254
|
+
}
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
assert(page.match(/logged in/));
|
|
258
|
+
|
|
259
|
+
// Marking a user account as disabled should invalidate sessions
|
|
260
|
+
user.disabled = true;
|
|
261
|
+
await apos.user.update(req, user);
|
|
262
|
+
|
|
263
|
+
page = await apos.http.get(
|
|
264
|
+
'/',
|
|
265
|
+
{
|
|
266
|
+
jar
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
assert(page.match(/logged out/));
|
|
271
|
+
|
|
272
|
+
// Restore access for next test
|
|
273
|
+
user.disabled = false;
|
|
274
|
+
await apos.user.update(req, user);
|
|
275
|
+
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('Changing a user\'s password should invalidate bearer tokens for that user', async function() {
|
|
279
|
+
|
|
280
|
+
// Log in
|
|
281
|
+
let response = await apos.http.post('/api/v1/@apostrophecms/login/login', {
|
|
282
|
+
body: {
|
|
283
|
+
username: 'HarryPutter',
|
|
284
|
+
password: 'VeryPasswordManySecure🐶'
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
assert(response.token);
|
|
288
|
+
let token = response.token;
|
|
289
|
+
|
|
290
|
+
// For verification: can't do this without an admin bearer token
|
|
291
|
+
await apos.http.get(
|
|
292
|
+
'/api/v1/@apostrophecms/user',
|
|
293
|
+
{
|
|
294
|
+
headers: {
|
|
295
|
+
Authorization: `Bearer ${token}`
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const req = apos.task.getReq();
|
|
301
|
+
const user = await apos.user.find(req, {
|
|
302
|
+
username: 'HarryPutter'
|
|
303
|
+
}).toObject();
|
|
304
|
+
assert(user);
|
|
305
|
+
user.password = 'AnotherLovelyPassword';
|
|
306
|
+
await apos.user.update(req, user);
|
|
307
|
+
|
|
308
|
+
let failed = false;
|
|
309
|
+
try {
|
|
310
|
+
await apos.http.get(
|
|
311
|
+
'/api/v1/@apostrophecms/user',
|
|
312
|
+
{
|
|
313
|
+
headers: {
|
|
314
|
+
Authorization: `Bearer ${token}`
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
// Should NOT work
|
|
319
|
+
assert(false);
|
|
320
|
+
} catch (e) {
|
|
321
|
+
failed = true;
|
|
322
|
+
assert.strictEqual(e.status, 401);
|
|
323
|
+
}
|
|
324
|
+
assert(failed);
|
|
325
|
+
|
|
326
|
+
// Make sure we can come back from that
|
|
327
|
+
response = await apos.http.post('/api/v1/@apostrophecms/login/login', {
|
|
328
|
+
body: {
|
|
329
|
+
username: 'HarryPutter',
|
|
330
|
+
password: 'AnotherLovelyPassword'
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
assert(response.token);
|
|
334
|
+
token = response.token;
|
|
335
|
+
|
|
336
|
+
await apos.http.get(
|
|
337
|
+
'/api/v1/@apostrophecms/user',
|
|
338
|
+
{
|
|
339
|
+
headers: {
|
|
340
|
+
Authorization: `Bearer ${token}`
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
});
|
|
346
|
+
|
|
164
347
|
});
|
package/test/moog.js
CHANGED
|
@@ -237,6 +237,53 @@ describe('moog', function() {
|
|
|
237
237
|
assert(myObject.extended(5) === 20);
|
|
238
238
|
});
|
|
239
239
|
|
|
240
|
+
it('should support inheriting field group fields rather than requiring all fields to be restated', async function() {
|
|
241
|
+
const moog = require('../lib/moog.js')({});
|
|
242
|
+
|
|
243
|
+
moog.define('myObject', {
|
|
244
|
+
cascades: [ 'fields' ],
|
|
245
|
+
fields: {
|
|
246
|
+
add: {
|
|
247
|
+
one: { type: 'string' },
|
|
248
|
+
two: { type: 'string' },
|
|
249
|
+
three: { type: 'string' }
|
|
250
|
+
},
|
|
251
|
+
group: {
|
|
252
|
+
basics: {
|
|
253
|
+
fields: [ 'one', 'two', 'three' ]
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
moog.define('myObject', {
|
|
260
|
+
fields: {
|
|
261
|
+
add: {
|
|
262
|
+
four: { type: 'string' },
|
|
263
|
+
five: { type: 'string' }
|
|
264
|
+
},
|
|
265
|
+
group: {
|
|
266
|
+
basics: {
|
|
267
|
+
fields: [ 'four', 'five' ]
|
|
268
|
+
},
|
|
269
|
+
other: {
|
|
270
|
+
fields: [ 'one' ]
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const myObject = await moog.create('myObject', {});
|
|
277
|
+
assert(myObject);
|
|
278
|
+
assert(myObject.fieldsGroups);
|
|
279
|
+
assert(!myObject.fieldsGroups.basics.fields.includes('one'));
|
|
280
|
+
assert(myObject.fieldsGroups.other.fields.includes('one'));
|
|
281
|
+
assert(myObject.fieldsGroups.basics.fields.includes('two'));
|
|
282
|
+
assert(myObject.fieldsGroups.basics.fields.includes('three'));
|
|
283
|
+
assert(myObject.fieldsGroups.basics.fields.includes('four'));
|
|
284
|
+
assert(myObject.fieldsGroups.basics.fields.includes('five'));
|
|
285
|
+
});
|
|
286
|
+
|
|
240
287
|
// ==================================================
|
|
241
288
|
// `redefine` AND `isDefined`
|
|
242
289
|
// ==================================================
|
package/test/package.json
CHANGED
package/test/pieces.js
CHANGED
|
@@ -768,6 +768,23 @@ describe('Pieces', function() {
|
|
|
768
768
|
relatedArticleId = response._articles[0]._id;
|
|
769
769
|
});
|
|
770
770
|
|
|
771
|
+
it('can GET a single product using projections', async () => {
|
|
772
|
+
const response = await apos.http.get(`/api/v1/product/${relatedProductId}`, {
|
|
773
|
+
qs: {
|
|
774
|
+
project: {
|
|
775
|
+
_id: 1,
|
|
776
|
+
title: 1
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
const keys = Object.keys(response);
|
|
782
|
+
|
|
783
|
+
assert(response);
|
|
784
|
+
assert(keys.length === 2);
|
|
785
|
+
assert(keys.every((key) => [ '_id', 'title' ].includes(key)));
|
|
786
|
+
});
|
|
787
|
+
|
|
771
788
|
it('can GET a single article with reverse relationships', async () => {
|
|
772
789
|
const response = await apos.http.get(`/api/v1/article/${relatedArticleId}`);
|
|
773
790
|
assert(response);
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// Bug report that motivated these tests:
|
|
2
|
+
//
|
|
3
|
+
// If products are related to salespeople, and salespeople have a reverse relationship
|
|
4
|
+
// back to products allowing that relationship to be viewed from the other end, everything
|
|
5
|
+
// works.
|
|
6
|
+
//
|
|
7
|
+
// But if products are also related to locations, the reverse relationship back from
|
|
8
|
+
// salespeople stops working.
|
|
9
|
+
|
|
10
|
+
const t = require('../test-lib/test.js');
|
|
11
|
+
const assert = require('assert');
|
|
12
|
+
|
|
13
|
+
describe('Basic reverse relationships', function() {
|
|
14
|
+
|
|
15
|
+
this.timeout(t.timeout);
|
|
16
|
+
|
|
17
|
+
it('basic reverse relationship query works', async function () {
|
|
18
|
+
let apos;
|
|
19
|
+
try {
|
|
20
|
+
apos = await t.create({
|
|
21
|
+
root: module,
|
|
22
|
+
modules: {
|
|
23
|
+
product: {
|
|
24
|
+
options: {
|
|
25
|
+
alias: 'product'
|
|
26
|
+
},
|
|
27
|
+
extend: '@apostrophecms/piece-type',
|
|
28
|
+
fields: {
|
|
29
|
+
add: {
|
|
30
|
+
_salespeople: {
|
|
31
|
+
type: 'relationship',
|
|
32
|
+
withType: 'salesperson'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
salesperson: {
|
|
38
|
+
options: {
|
|
39
|
+
alias: 'salesperson'
|
|
40
|
+
},
|
|
41
|
+
extend: '@apostrophecms/piece-type',
|
|
42
|
+
fields: {
|
|
43
|
+
add: {
|
|
44
|
+
_products: {
|
|
45
|
+
type: 'relationshipReverse',
|
|
46
|
+
withType: 'product',
|
|
47
|
+
reverseOf: '_salespeople'
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const req = apos.task.getReq();
|
|
56
|
+
const salesperson = await apos.salesperson.insert(req, {
|
|
57
|
+
title: 'Willie Loman'
|
|
58
|
+
});
|
|
59
|
+
await apos.salesperson.insert(req, {
|
|
60
|
+
title: 'Bernie Sanders'
|
|
61
|
+
});
|
|
62
|
+
await apos.product.insert(req, {
|
|
63
|
+
title: 'Soap',
|
|
64
|
+
_salespeople: [ salesperson ]
|
|
65
|
+
});
|
|
66
|
+
await apos.product.insert(req, {
|
|
67
|
+
title: 'Rope'
|
|
68
|
+
});
|
|
69
|
+
const fetched = await apos.salesperson.find(req, {
|
|
70
|
+
title: 'Willie Loman'
|
|
71
|
+
}).toObject();
|
|
72
|
+
assert(fetched);
|
|
73
|
+
assert.strictEqual(fetched.title, 'Willie Loman');
|
|
74
|
+
assert(fetched._products);
|
|
75
|
+
assert.strictEqual(fetched._products.length, 1);
|
|
76
|
+
assert.strictEqual(fetched._products[0].title, 'Soap');
|
|
77
|
+
} finally {
|
|
78
|
+
if (apos) {
|
|
79
|
+
await t.destroy(apos);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('Reverse relationships plus an extra relationship', function() {
|
|
86
|
+
|
|
87
|
+
this.timeout(t.timeout);
|
|
88
|
+
|
|
89
|
+
it('basic reverse relationship query works in the presence of an extra relationship with the types configured in an unexpected order', async function () {
|
|
90
|
+
let apos;
|
|
91
|
+
try {
|
|
92
|
+
apos = await t.create({
|
|
93
|
+
root: module,
|
|
94
|
+
modules: {
|
|
95
|
+
salesperson: {
|
|
96
|
+
options: {
|
|
97
|
+
alias: 'salesperson'
|
|
98
|
+
},
|
|
99
|
+
extend: '@apostrophecms/piece-type',
|
|
100
|
+
fields: {
|
|
101
|
+
add: {
|
|
102
|
+
_products: {
|
|
103
|
+
type: 'relationshipReverse',
|
|
104
|
+
withType: 'product',
|
|
105
|
+
reverseOf: '_salespeople'
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
location: {
|
|
111
|
+
options: {
|
|
112
|
+
alias: 'location'
|
|
113
|
+
},
|
|
114
|
+
extend: '@apostrophecms/piece-type'
|
|
115
|
+
},
|
|
116
|
+
product: {
|
|
117
|
+
options: {
|
|
118
|
+
alias: 'product'
|
|
119
|
+
},
|
|
120
|
+
extend: '@apostrophecms/piece-type',
|
|
121
|
+
fields: {
|
|
122
|
+
add: {
|
|
123
|
+
_salespeople: {
|
|
124
|
+
type: 'relationship',
|
|
125
|
+
withType: 'salesperson'
|
|
126
|
+
},
|
|
127
|
+
_location: {
|
|
128
|
+
type: 'relationship',
|
|
129
|
+
withType: 'location'
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const req = apos.task.getReq();
|
|
138
|
+
const salesperson = await apos.salesperson.insert(req, {
|
|
139
|
+
title: 'Willie Loman'
|
|
140
|
+
});
|
|
141
|
+
await apos.salesperson.insert(req, {
|
|
142
|
+
title: 'Bernie Sanders'
|
|
143
|
+
});
|
|
144
|
+
await apos.product.insert(req, {
|
|
145
|
+
title: 'Soap',
|
|
146
|
+
_salespeople: [ salesperson ]
|
|
147
|
+
});
|
|
148
|
+
await apos.product.insert(req, {
|
|
149
|
+
title: 'Rope'
|
|
150
|
+
});
|
|
151
|
+
const fetched = await apos.salesperson.find(req, {
|
|
152
|
+
title: 'Willie Loman'
|
|
153
|
+
}).toObject();
|
|
154
|
+
const soap = await apos.product.find(req, {
|
|
155
|
+
title: 'Soap'
|
|
156
|
+
}).toObject();
|
|
157
|
+
assert(fetched);
|
|
158
|
+
assert.strictEqual(fetched.title, 'Willie Loman');
|
|
159
|
+
assert(fetched._products);
|
|
160
|
+
assert.strictEqual(fetched._products.length, 1);
|
|
161
|
+
assert.strictEqual(fetched._products[0].title, 'Soap');
|
|
162
|
+
assert.strictEqual(soap.title, 'Soap');
|
|
163
|
+
assert.strictEqual(soap._salespeople[0].title, 'Willie Loman');
|
|
164
|
+
} finally {
|
|
165
|
+
if (apos) {
|
|
166
|
+
await t.destroy(apos);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const t = require('../test-lib/test.js');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
describe('Project with package.json in its parent folder works', function() {
|
|
5
|
+
|
|
6
|
+
this.timeout(t.timeout);
|
|
7
|
+
|
|
8
|
+
/// ///
|
|
9
|
+
// EXISTENCE
|
|
10
|
+
/// ///
|
|
11
|
+
|
|
12
|
+
it('should allow a project relying on a package.json in its parent folder', async function() {
|
|
13
|
+
let apos;
|
|
14
|
+
try {
|
|
15
|
+
apos = await t.create(require('./subdir-project/app.js'));
|
|
16
|
+
// Sniff test: a normal apos object
|
|
17
|
+
assert(apos.user);
|
|
18
|
+
} finally {
|
|
19
|
+
if (apos) {
|
|
20
|
+
await t.destroy(apos);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
assert(apos);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
});
|
package/test/templates.js
CHANGED
|
@@ -182,7 +182,6 @@ describe('Templates', function() {
|
|
|
182
182
|
it('should render fragments containing async components correctly', async () => {
|
|
183
183
|
const req = apos.task.getReq();
|
|
184
184
|
const result = await apos.modules['fragment-page'].renderPage(req, 'page');
|
|
185
|
-
|
|
186
185
|
const aboveFragment = result.indexOf('Above Fragment');
|
|
187
186
|
const beforeComponent = result.indexOf('Before Component');
|
|
188
187
|
const componentText = result.indexOf('Component Text');
|
|
@@ -295,4 +294,11 @@ describe('Templates', function() {
|
|
|
295
294
|
]);
|
|
296
295
|
});
|
|
297
296
|
|
|
297
|
+
it('should support apos helpers and localization in fragments', async () => {
|
|
298
|
+
const req = apos.task.getReq();
|
|
299
|
+
const result = await apos.modules['fragment-all'].renderPage(req, 'aux-test');
|
|
300
|
+
assert(result.includes('gee-whiz'));
|
|
301
|
+
assert(result.includes('Modify / Delete'));
|
|
302
|
+
});
|
|
303
|
+
|
|
298
304
|
});
|
package/test-lib/test.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
const fs = require('fs-extra');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
fs.
|
|
6
|
-
fs.
|
|
4
|
+
const testNodeModules = path.join(__dirname, '/../test/node_modules');
|
|
5
|
+
fs.removeSync(testNodeModules);
|
|
6
|
+
fs.mkdirSync(testNodeModules);
|
|
7
|
+
fs.symlinkSync(path.join(__dirname, '/..'), path.join(testNodeModules, 'apostrophe'), 'dir');
|
|
8
|
+
|
|
9
|
+
const extras = path.join(__dirname, '../test/extra_node_modules');
|
|
10
|
+
const dirs = fs.existsSync(extras) ? fs.readdirSync(extras) : [];
|
|
11
|
+
for (const dir of dirs) {
|
|
12
|
+
fs.symlinkSync(path.join(extras, dir), path.join(testNodeModules, dir), 'dir');
|
|
13
|
+
}
|
|
7
14
|
|
|
8
15
|
// Need a "project level" package.json for functionality that checks
|
|
9
16
|
// whether packages in node_modules are project level or not
|
|
@@ -12,15 +19,19 @@ const packageJson = path.join(__dirname, '/../test/package.json');
|
|
|
12
19
|
// Remove it first, in case it's the old-style symlink to the main package.json,
|
|
13
20
|
// which would break
|
|
14
21
|
fs.removeSync(packageJson);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"apostrophe": "^3.0.0"
|
|
22
|
+
const packageJsonInfo = {
|
|
23
|
+
name: 'test',
|
|
24
|
+
dependencies: {
|
|
25
|
+
apostrophe: '^3.0.0'
|
|
20
26
|
},
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
27
|
+
devDependencies: {
|
|
28
|
+
'test-bundle': '1.0.0'
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
for (const dir of dirs) {
|
|
32
|
+
packageJsonInfo.dependencies[dir] = '1.0.0';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fs.writeFileSync(packageJson, JSON.stringify(packageJsonInfo, null, ' '));
|
|
25
36
|
|
|
26
37
|
module.exports = require('./util.js');
|
package/test-lib/util.js
CHANGED
|
@@ -37,6 +37,7 @@ async function create(options) {
|
|
|
37
37
|
_: [],
|
|
38
38
|
'ignore-orphan-modules': true
|
|
39
39
|
},
|
|
40
|
+
test: true,
|
|
40
41
|
autoBuild: false,
|
|
41
42
|
...options
|
|
42
43
|
};
|
|
@@ -56,8 +57,40 @@ async function create(options) {
|
|
|
56
57
|
return require('../index.js')(config);
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
// Create an admin user. By default the username and password are both 'admin'
|
|
61
|
+
async function createAdmin(apos, { username, password } = {}) {
|
|
62
|
+
const user = apos.user.newInstance();
|
|
63
|
+
const name = username || 'admin';
|
|
64
|
+
|
|
65
|
+
user.title = name;
|
|
66
|
+
user.username = name;
|
|
67
|
+
user.password = password || 'admin';
|
|
68
|
+
user.email = `${name}@admin.io`;
|
|
69
|
+
user.role = 'admin';
|
|
70
|
+
|
|
71
|
+
return await apos.user.insert(apos.task.getReq(), user);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function getUserJar(apos, { username, password } = {}) {
|
|
75
|
+
const jar = apos.http.jar();
|
|
76
|
+
|
|
77
|
+
// Log in
|
|
78
|
+
await apos.http.post('/api/v1/@apostrophecms/login/login', {
|
|
79
|
+
body: {
|
|
80
|
+
username: username || 'admin',
|
|
81
|
+
password: password || 'admin',
|
|
82
|
+
session: true
|
|
83
|
+
},
|
|
84
|
+
jar
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return jar;
|
|
88
|
+
}
|
|
89
|
+
|
|
59
90
|
module.exports = {
|
|
60
91
|
destroy,
|
|
61
92
|
create,
|
|
93
|
+
createAdmin,
|
|
94
|
+
getUserJar,
|
|
62
95
|
timeout: (process.env.TEST_TIMEOUT && parseInt(process.env.TEST_TIMEOUT)) || 20000
|
|
63
96
|
};
|