@saltcorn/server 0.9.0-beta.1 → 0.9.0-beta.11

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.
@@ -568,11 +568,11 @@ describe("tags", () => {
568
568
  .post("/tag")
569
569
  .set("Cookie", loginCookie)
570
570
  .send("name=MyNewTestTag")
571
- .expect(toRedirect("/tag/1?show_list=tables"));
571
+ .expect(toRedirect("/tag/2?show_list=tables"));
572
572
  });
573
573
 
574
574
  itShouldIncludeTextForAdmin("/tag", "MyNewTestTag");
575
- itShouldIncludeTextForAdmin("/tag/1", "MyNewTestTag");
575
+ itShouldIncludeTextForAdmin("/tag/2", "MyNewTestTag");
576
576
  itShouldIncludeTextForAdmin("/tag-entries/add/tables/1", "Add entries");
577
577
  itShouldIncludeTextForAdmin("/tag-entries/add/pages/1", "Add entries");
578
578
  itShouldIncludeTextForAdmin("/tag-entries/add/views/1", "Add entries");
@@ -5,11 +5,15 @@ const {
5
5
  getAdminLoginCookie,
6
6
  resetToFixtures,
7
7
  respondJsonWith,
8
+ toRedirect,
9
+ toInclude,
10
+ toSucceed,
8
11
  } = require("../auth/testhelp");
9
12
  const db = require("@saltcorn/data/db");
10
13
  const { sleep } = require("@saltcorn/data/tests/mocks");
11
14
 
12
15
  const Table = require("@saltcorn/data/models/table");
16
+ const TableConstraint = require("@saltcorn/data/models/table_constraints");
13
17
  const Field = require("@saltcorn/data/models/field");
14
18
  const User = require("@saltcorn/data/models/user");
15
19
 
@@ -181,6 +185,52 @@ describe("load remote insert/updates", () => {
181
185
  }
182
186
  });
183
187
 
188
+ it("sync table with capitals", async () => {
189
+ const app = await getApp({ disableCsrf: true });
190
+ const loginCookie = await getAdminLoginCookie();
191
+ // create table
192
+ await request(app)
193
+ .post("/table")
194
+ .set("Cookie", loginCookie)
195
+ .send(`name=${encodeURIComponent("Table with capitals")}`)
196
+ .expect(toRedirect("/table/16"));
197
+ // add a field
198
+ await request(app)
199
+ .post("/field/")
200
+ .send("stepName=Basic properties")
201
+ .send("name=string_field")
202
+ .send("label=StringField")
203
+ .send("type=String")
204
+ .send(
205
+ `contextEnc=${encodeURIComponent(JSON.stringify({ table_id: 16 }))}`
206
+ )
207
+ .set("Cookie", loginCookie)
208
+ .expect(toInclude("options"));
209
+ // init sync_info table
210
+ await request(app)
211
+ .post("/table")
212
+ .send("id=16")
213
+ .send("has_sync_info=on")
214
+ .set("Cookie", loginCookie)
215
+ .expect(toRedirect("/table/16"));
216
+ const dbTime = await db.time();
217
+
218
+ // call load changes
219
+ await request(app)
220
+ .post("/sync/load_changes")
221
+ .set("Cookie", loginCookie)
222
+ .send({
223
+ loadUntil: (await db.time()).valueOf(),
224
+ syncInfos: {
225
+ "Table with capitals": {
226
+ maxLoadedId: 0,
227
+ syncFrom: dbTime.valueOf(),
228
+ },
229
+ },
230
+ })
231
+ .expect(toSucceed());
232
+ });
233
+
184
234
  it("load sync not authorized", async () => {
185
235
  const app = await getApp({ disableCsrf: true });
186
236
  const loginCookie = await getUserLoginCookie();
@@ -300,8 +350,9 @@ describe("Upload changes", () => {
300
350
  .get(`/sync/upload_finished?dir_name=${encodeURIComponent(syncDir)}`)
301
351
  .set("Cookie", loginCookie);
302
352
  expect(resp.status).toBe(200);
303
- const { finished, translatedIds, error } = resp._body;
304
- if (finished) return translatedIds ? translatedIds : error;
353
+ const { finished, translatedIds, uniqueConflicts, error } = resp._body;
354
+ if (finished)
355
+ return translatedIds ? { translatedIds, uniqueConflicts } : error;
305
356
  await sleep(1000);
306
357
  }
307
358
  return null;
@@ -352,7 +403,7 @@ describe("Upload changes", () => {
352
403
  });
353
404
  expect(resp.status).toBe(200);
354
405
  const { syncDir } = resp._body;
355
- const translatedIds = await getResult(app, loginCookie, syncDir);
406
+ const { translatedIds } = await getResult(app, loginCookie, syncDir);
356
407
  await cleanSyncDir(app, loginCookie, syncDir);
357
408
  expect(translatedIds).toBeDefined();
358
409
  expect(translatedIds).toEqual({
@@ -365,6 +416,89 @@ describe("Upload changes", () => {
365
416
  });
366
417
  });
367
418
 
419
+ it("handles inserts with TableConstraint conflicts", async () => {
420
+ const books = Table.findOne({ name: "books" });
421
+ const oldCount = await books.countRows();
422
+ // unique constraint for author + pages
423
+ const constraint = await TableConstraint.create({
424
+ table: books,
425
+ type: "Unique",
426
+ configuration: {
427
+ fields: ["author", "pages"],
428
+ },
429
+ });
430
+
431
+ const app = await getApp({ disableCsrf: true });
432
+ const loginCookie = await getAdminLoginCookie();
433
+ const resp = await doUpload(app, loginCookie, new Date().valueOf(), {
434
+ books: {
435
+ inserts: [
436
+ {
437
+ author: "Herman Melville",
438
+ pages: 967,
439
+ publisher: 1,
440
+ },
441
+ {
442
+ author: "Leo Tolstoy",
443
+ pages: "728",
444
+ publisher: 2,
445
+ },
446
+ ],
447
+ },
448
+ });
449
+
450
+ expect(resp.status).toBe(200);
451
+ const { syncDir } = resp._body;
452
+ const { uniqueConflicts } = await getResult(app, loginCookie, syncDir);
453
+ await constraint.delete();
454
+ await cleanSyncDir(app, loginCookie, syncDir);
455
+ expect(uniqueConflicts).toBeDefined();
456
+ expect(uniqueConflicts).toEqual({
457
+ books: [
458
+ { id: 1, author: "Herman Melville", pages: 967, publisher: null },
459
+ { id: 2, author: "Leo Tolstoy", pages: 728, publisher: 1 },
460
+ ],
461
+ });
462
+ const newCount = await books.countRows();
463
+ expect(newCount).toBe(oldCount);
464
+ });
465
+
466
+ it("denies updates with TableConstraint conflicts", async () => {
467
+ const books = Table.findOne({ name: "books" });
468
+ const oldCount = await books.countRows();
469
+ // unique constraint for author + pages
470
+ const constraint = await TableConstraint.create({
471
+ table: books,
472
+ type: "Unique",
473
+ configuration: {
474
+ fields: ["author", "pages"],
475
+ },
476
+ });
477
+
478
+ const app = await getApp({ disableCsrf: true });
479
+ const loginCookie = await getAdminLoginCookie();
480
+ const resp = await doUpload(app, loginCookie, new Date().valueOf(), {
481
+ books: {
482
+ updates: [
483
+ {
484
+ id: 2,
485
+ author: "Herman Melville",
486
+ pages: 967,
487
+ },
488
+ ],
489
+ },
490
+ });
491
+ expect(resp.status).toBe(200);
492
+ const { syncDir } = resp._body;
493
+ const error = await getResult(app, loginCookie, syncDir);
494
+ await constraint.delete();
495
+ await cleanSyncDir(app, loginCookie, syncDir);
496
+ expect(error).toBeDefined();
497
+ expect(error).toEqual({
498
+ message: "Duplicate value for unique field: author_pages",
499
+ });
500
+ });
501
+
368
502
  it("update with translation", async () => {
369
503
  const app = await getApp({ disableCsrf: true });
370
504
  const loginCookie = await getAdminLoginCookie();
@@ -389,7 +523,7 @@ describe("Upload changes", () => {
389
523
  });
390
524
  expect(resp.status).toBe(200);
391
525
  const { syncDir } = resp._body;
392
- const translatedIds = await getResult(app, loginCookie, syncDir);
526
+ const { translatedIds } = await getResult(app, loginCookie, syncDir);
393
527
  await cleanSyncDir(app, loginCookie, syncDir);
394
528
  expect(translatedIds).toBeDefined();
395
529
  expect(translatedIds).toEqual({
@@ -427,7 +561,7 @@ describe("Upload changes", () => {
427
561
  });
428
562
  expect(resp.status).toBe(200);
429
563
  const { syncDir } = resp._body;
430
- const translatedIds = await getResult(app, loginCookie, syncDir);
564
+ const { translatedIds } = await getResult(app, loginCookie, syncDir);
431
565
  await cleanSyncDir(app, loginCookie, syncDir);
432
566
  expect(translatedIds).toBeDefined();
433
567
  const afterDelete = await books.getRows();
@@ -471,7 +605,7 @@ describe("Upload changes", () => {
471
605
  });
472
606
  expect(resp.status).toBe(200);
473
607
  const { syncDir } = resp._body;
474
- const translatedIds = await getResult(app, loginCookie, syncDir);
608
+ const { translatedIds } = await getResult(app, loginCookie, syncDir);
475
609
  await cleanSyncDir(app, loginCookie, syncDir);
476
610
  expect(translatedIds).toBeDefined();
477
611
  const afterDelete = await books.getRows();
@@ -396,7 +396,7 @@ describe("viewedit new Show", () => {
396
396
  it("delete new view", async () => {
397
397
  const loginCookie = await getAdminLoginCookie();
398
398
  const app = await getApp({ disableCsrf: true });
399
- const id = (await View.findOne({ name: "mybook" })).id;
399
+ const id = View.findOne({ name: "mybook" }).id;
400
400
 
401
401
  await request(app)
402
402
  .post("/viewedit/delete/" + id)
@@ -404,6 +404,249 @@ describe("viewedit new Show", () => {
404
404
  .expect(toRedirect("/viewedit"));
405
405
  });
406
406
  });
407
+
408
+ describe("viewedit new Edit", () => {
409
+ // create two edit views for 'books'
410
+ // the first has inputs for all books-fields
411
+ // => 'Fixed and blocked fields (step 2 / max 3)' won't show up
412
+ // the second has no input for 'pages'
413
+ // => 'Fixed and blocked fields (step 2 / max 3)' shows up
414
+ // and the configration gets a fixed property
415
+ const colsWithoutPages = [
416
+ {
417
+ type: "Field",
418
+ fieldview: "edit",
419
+ field_name: "author",
420
+ },
421
+ {
422
+ type: "Field",
423
+ fieldview: "select",
424
+ field_name: "publisher",
425
+ },
426
+ {
427
+ type: "Action",
428
+ rndid: "f61f38",
429
+ minRole: 100,
430
+ action_name: "Save",
431
+ action_style: "btn-primary",
432
+ },
433
+ ];
434
+ const colsWithPages = [
435
+ {
436
+ type: "Field",
437
+ fieldview: "edit",
438
+ field_name: "pages",
439
+ },
440
+ ...colsWithoutPages,
441
+ ];
442
+
443
+ const layoutWithoutPages = {
444
+ above: [
445
+ {
446
+ type: "field",
447
+ fieldview: "edit",
448
+ field_name: "author",
449
+ },
450
+
451
+ {
452
+ type: "field",
453
+ fieldview: "select",
454
+ field_name: "publisher",
455
+ },
456
+ {
457
+ type: "action",
458
+ rndid: "f61f38",
459
+ minRole: 100,
460
+ action_name: "Save",
461
+ action_style: "btn-primary",
462
+ },
463
+ ],
464
+ };
465
+ const layoutWithPages = {
466
+ above: [
467
+ {
468
+ type: "field",
469
+ fieldview: "edit",
470
+ field_name: "pages",
471
+ },
472
+ ...layoutWithoutPages.above,
473
+ ],
474
+ };
475
+
476
+ it("submits new view", async () => {
477
+ const loginCookie = await getAdminLoginCookie();
478
+ const app = await getApp({ disableCsrf: true });
479
+ // edit_mybook
480
+ await request(app)
481
+ .post("/viewedit/save")
482
+ .send("viewtemplate=Edit")
483
+ .send("table_name=books")
484
+ .send("name=edit_mybook")
485
+ .send("min_role=100")
486
+ .set("Cookie", loginCookie)
487
+ .expect(toRedirect("/viewedit/config/edit_mybook"));
488
+ // edit_mybook_without_pages
489
+ await request(app)
490
+ .post("/viewedit/save")
491
+ .send("viewtemplate=Edit")
492
+ .send("table_name=books")
493
+ .send("name=edit_mybook_without_pages")
494
+ .send("min_role=100")
495
+ .set("Cookie", loginCookie)
496
+ .expect(toRedirect("/viewedit/config/edit_mybook_without_pages"));
497
+ });
498
+
499
+ it("saves new view layout", async () => {
500
+ const loginCookie = await getAdminLoginCookie();
501
+ const table = Table.findOne({ name: "books" });
502
+ const app = await getApp({ disableCsrf: true });
503
+ // edit_mybook
504
+ await request(app)
505
+ .post("/viewedit/config/edit_mybook")
506
+ .send(
507
+ "contextEnc=" +
508
+ encodeURIComponent(
509
+ JSON.stringify({
510
+ table_id: table.id,
511
+ viewname: "edit_mybook",
512
+ })
513
+ )
514
+ )
515
+ .send("stepName=Layout")
516
+ .send("columns=" + encodeURIComponent(JSON.stringify(colsWithPages)))
517
+ .send("layout=" + encodeURIComponent(JSON.stringify(layoutWithPages)))
518
+ .set("Cookie", loginCookie)
519
+ .expect(toInclude("Edit options (step 3 / 3)"));
520
+ // edit_mybook_without_pages
521
+ await request(app)
522
+ .post("/viewedit/config/edit_mybook_without_pages")
523
+ .send(
524
+ "contextEnc=" +
525
+ encodeURIComponent(
526
+ JSON.stringify({
527
+ table_id: table.id,
528
+ viewname: "edit_mybook_without_pages",
529
+ })
530
+ )
531
+ )
532
+ .send("stepName=Layout")
533
+ .send("columns=" + encodeURIComponent(JSON.stringify(colsWithoutPages)))
534
+ .send("layout=" + encodeURIComponent(JSON.stringify(layoutWithoutPages)))
535
+ .set("Cookie", loginCookie)
536
+ .expect(toInclude("Fixed and blocked fields (step 2 / max 3)"));
537
+ });
538
+
539
+ it("saves new view fixed fields", async () => {
540
+ const loginCookie = await getAdminLoginCookie();
541
+ const table = Table.findOne({ name: "books" });
542
+ const app = await getApp({ disableCsrf: true });
543
+ // only edit_mybook_without_pages
544
+ await request(app)
545
+ .post("/viewedit/config/edit_mybook_without_pages")
546
+ .send(
547
+ "contextEnc=" +
548
+ encodeURIComponent(
549
+ JSON.stringify({
550
+ table_id: table.id,
551
+ viewname: "edit_mybook_without_pages",
552
+ layout: layoutWithoutPages,
553
+ columns: colsWithoutPages,
554
+ })
555
+ )
556
+ )
557
+ .send("stepName=Fixed+and+blocked+fields")
558
+ .send("pages=2")
559
+ .set("Cookie", loginCookie)
560
+ .expect(toInclude("Edit options (step 3 / 3)"));
561
+ });
562
+
563
+ it("saves view when done", async () => {
564
+ const loginCookie = await getAdminLoginCookie();
565
+ const table = Table.findOne({ name: "books" });
566
+ const app = await getApp({ disableCsrf: true });
567
+ // edit_mybook
568
+ await request(app)
569
+ .post("/viewedit/config/edit_mybook")
570
+ .send(
571
+ "contextEnc=" +
572
+ encodeURIComponent(
573
+ JSON.stringify({
574
+ table_id: table.id,
575
+ viewname: "edit_mybook",
576
+ layout: layoutWithPages,
577
+ columns: colsWithPages,
578
+ })
579
+ )
580
+ )
581
+ .send("stepName=Edit+options")
582
+ .send("destination_type=View")
583
+ .send("view_when_done=authorlist")
584
+ .send("auto_save=on")
585
+ .send("split_paste=on")
586
+ .set("Cookie", loginCookie)
587
+ .expect(toRedirect("/viewedit"));
588
+ const viewWithPages = View.findOne({ name: "edit_mybook" });
589
+ expect(viewWithPages.configuration.layout).toEqual(layoutWithPages);
590
+ expect(viewWithPages.configuration.columns).toEqual(colsWithPages);
591
+ // edit_mybook_without_pages
592
+ await request(app)
593
+ .post("/viewedit/config/edit_mybook_without_pages")
594
+ .send(
595
+ "contextEnc=" +
596
+ encodeURIComponent(
597
+ JSON.stringify({
598
+ table_id: table.id,
599
+ viewname: "edit_mybook_without_pages",
600
+ layout: layoutWithoutPages,
601
+ columns: colsWithoutPages,
602
+ fixed: {
603
+ pages: 22,
604
+ _block_pages: false,
605
+ },
606
+ })
607
+ )
608
+ )
609
+ .send("stepName=Edit+options")
610
+ .send("destination_type=View")
611
+ .send("view_when_done=authorlist")
612
+ .send("auto_save=on")
613
+ .send("split_paste=on")
614
+ .set("Cookie", loginCookie)
615
+ .expect(toRedirect("/viewedit"));
616
+
617
+ const viewWithoutPages = View.findOne({
618
+ name: "edit_mybook_without_pages",
619
+ });
620
+ expect(viewWithoutPages.configuration.fixed).toEqual({
621
+ pages: 22,
622
+ _block_pages: false,
623
+ });
624
+ expect(viewWithoutPages.configuration.layout).toEqual(layoutWithoutPages);
625
+ expect(viewWithoutPages.configuration.columns).toEqual(colsWithoutPages);
626
+ });
627
+
628
+ it("deletes new view", async () => {
629
+ const loginCookie = await getAdminLoginCookie();
630
+ const app = await getApp({ disableCsrf: true });
631
+ const idA = View.findOne({ name: "edit_mybook" }).id;
632
+ // edit_mybook
633
+ await request(app)
634
+ .post("/viewedit/delete/" + idA)
635
+ .set("Cookie", loginCookie)
636
+ .expect(toRedirect("/viewedit"));
637
+ // edit_mybook_without_pages
638
+ const idB = View.findOne({ name: "edit_mybook_without_pages" }).id;
639
+ await request(app)
640
+ .post("/viewedit/delete/" + idB)
641
+ .set("Cookie", loginCookie)
642
+ .expect(toRedirect("/viewedit"));
643
+ });
644
+ });
645
+
646
+ describe("viewedit new Edit with fixed fields", () => {
647
+ it("submit new view", async () => {});
648
+ });
649
+
407
650
  describe("Library", () => {
408
651
  it("should save new from builder", async () => {
409
652
  const loginCookie = await getAdminLoginCookie();
package/wrapper.js CHANGED
@@ -187,7 +187,9 @@ const get_headers = (req, version_tag, description, extras = []) => {
187
187
  : [];
188
188
  const stdHeaders = [
189
189
  {
190
- headerTag: `<script>var _sc_globalCsrf = "${req.csrfToken()}"; var _sc_version_tag = "${version_tag}";</script>`,
190
+ headerTag: `<script>var _sc_loglevel = ${
191
+ state.logLevel
192
+ }, _sc_globalCsrf = "${req.csrfToken()}", _sc_version_tag = "${version_tag}";</script>`,
191
193
  },
192
194
  { css: `/static_assets/${version_tag}/saltcorn.css` },
193
195
  { script: `/static_assets/${version_tag}/saltcorn-common.js` },
@@ -309,6 +311,7 @@ module.exports = (version_tag) =>
309
311
  const alerts = getFlashes(req);
310
312
  const state = getState();
311
313
  const layout = state.getLayout(req.user);
314
+ const no_menu = opts.no_menu;
312
315
 
313
316
  if (req.xhr) {
314
317
  const renderToHtml = layout.renderBody
@@ -335,8 +338,8 @@ module.exports = (version_tag) =>
335
338
  res.send(
336
339
  layout.wrap({
337
340
  title,
338
- brand: get_brand(state),
339
- menu: get_menu(req),
341
+ brand: no_menu ? undefined : get_brand(state),
342
+ menu: no_menu ? undefined : get_menu(req),
340
343
  currentUrl,
341
344
  originalUrl: req.originalUrl,
342
345