@saltcorn/server 0.9.4-beta.0 → 0.9.4-beta.10

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/routes/tables.js CHANGED
@@ -13,6 +13,7 @@ const View = require("@saltcorn/data/models/view");
13
13
  const User = require("@saltcorn/data/models/user");
14
14
  const Model = require("@saltcorn/data/models/model");
15
15
  const Trigger = require("@saltcorn/data/models/trigger");
16
+ const TagEntry = require("@saltcorn/data/models/tag_entry");
16
17
  const {
17
18
  mkTable,
18
19
  renderForm,
@@ -59,10 +60,12 @@ const { tablesList, viewsList } = require("./common_lists");
59
60
  const {
60
61
  InvalidConfiguration,
61
62
  removeAllWhiteSpace,
63
+ comparingCaseInsensitive,
62
64
  } = require("@saltcorn/data/utils");
63
65
  const { EOL } = require("os");
64
66
 
65
67
  const path = require("path");
68
+ const Tag = require("@saltcorn/data/models/tag");
66
69
  /**
67
70
  * @type {object}
68
71
  * @const
@@ -714,6 +717,7 @@ router.get(
714
717
  ...new Set(child_relations.map(({ table }) => table.name)),
715
718
  ];
716
719
  const triggers = table.id ? Trigger.find({ table_id: table.id }) : [];
720
+ triggers.sort(comparingCaseInsensitive("name"));
717
721
  let fieldCard;
718
722
  if (fields.length === 0) {
719
723
  fieldCard = [
@@ -785,7 +789,16 @@ router.get(
785
789
  triggers.length
786
790
  ? req.__("Table triggers: ") +
787
791
  triggers
788
- .map((t) => link(`/actions/configure/${t.id}`, t.name))
792
+ .map((t) =>
793
+ link(
794
+ `/actions/configure/${
795
+ t.id
796
+ }?on_done_redirect=${encodeURIComponent(
797
+ `table/${table.name}`
798
+ )}`,
799
+ t.name
800
+ )
801
+ )
789
802
  .join(", ") +
790
803
  "<br>"
791
804
  : "",
@@ -1211,13 +1224,23 @@ router.get(
1211
1224
  "/",
1212
1225
  isAdmin,
1213
1226
  error_catcher(async (req, res) => {
1214
- const rows = await Table.find_with_external(
1215
- {},
1216
- { orderBy: "name", nocase: true }
1217
- );
1227
+ const tblq = {};
1228
+ let filterOnTag;
1229
+ if (req.query._tag) {
1230
+ const tagEntries = await TagEntry.find({
1231
+ tag_id: +req.query._tag,
1232
+ not: { table_id: null },
1233
+ });
1234
+ tblq.id = { in: tagEntries.map((te) => te.table_id).filter(Boolean) };
1235
+ filterOnTag = await Tag.findOne({ id: +req.query._tag });
1236
+ }
1237
+ const rows = await Table.find_with_external(tblq, {
1238
+ orderBy: "name",
1239
+ nocase: true,
1240
+ });
1218
1241
  const roles = await User.get_roles();
1219
1242
  const getRole = (rid) => roles.find((r) => r.id === rid).role;
1220
- const mainCard = await tablesList(rows, req);
1243
+ const mainCard = await tablesList(rows, req, { filterOnTag });
1221
1244
  const createCard = div(
1222
1245
  a(
1223
1246
  { href: `/table/new`, class: "btn btn-primary mt-1 me-3" },
@@ -5,6 +5,7 @@ const {
5
5
  select,
6
6
  option,
7
7
  label,
8
+ text,
8
9
  } = require("@saltcorn/markup/tags");
9
10
 
10
11
  const Tag = require("@saltcorn/data/models/tag");
@@ -94,15 +95,21 @@ router.get(
94
95
  isAdmin,
95
96
  error_catcher(async (req, res) => {
96
97
  const { entry_type, tag_id } = req.params;
97
- res.sendWrap(req.__("Add %s to tag"), {
98
+ const tag = await Tag.findOne({ id: tag_id });
99
+
100
+ res.sendWrap(req.__("Add %s to tag %s", entry_type, tag.name), {
98
101
  above: [
99
102
  {
100
103
  type: "breadcrumbs",
101
- crumbs: [{ text: req.__(`Tag entry`) }],
104
+ crumbs: [
105
+ { text: req.__(`Tags`), href: "/tag" },
106
+ { text: tag.name, href: `/tag/${tag.id}` },
107
+ { text: req.__(`Add %s`, text(entry_type)) },
108
+ ],
102
109
  },
103
110
  {
104
111
  type: "card",
105
- title: req.__(`Add entries to tag`),
112
+ title: req.__(`Add entries to tag %s`, tag.name),
106
113
  contents: buildForm(
107
114
  entry_type,
108
115
  tag_id,
@@ -148,9 +155,10 @@ router.post(
148
155
  req.flash("error", req.__("Please select at least one item"));
149
156
  return res.redirect(`/tag-entries/add/${entry_type}/${tag_id}`);
150
157
  }
158
+ const ids_array = Array.isArray(ids) ? ids : [ids];
151
159
  const fieldName = idField(entry_type);
152
160
  const tag = await Tag.findOne({ id: tag_id });
153
- for (const id of ids) {
161
+ for (const id of ids_array) {
154
162
  await tag.addEntry({ [fieldName]: id });
155
163
  }
156
164
  res.redirect(`/tag/${tag_id}?show_list=${entry_type}`);
package/routes/tags.js CHANGED
@@ -1,9 +1,10 @@
1
- const { a, text } = require("@saltcorn/markup/tags");
1
+ const { a, text, i } = require("@saltcorn/markup/tags");
2
2
 
3
3
  const Tag = require("@saltcorn/data/models/tag");
4
4
  const Router = require("express-promise-router");
5
5
  const Form = require("@saltcorn/data/models/form");
6
6
  const User = require("@saltcorn/data/models/user");
7
+ const stream = require("stream");
7
8
 
8
9
  const { isAdmin, error_catcher, csrfField } = require("./utils");
9
10
  const { send_infoarch_page } = require("../markup/admin");
@@ -23,6 +24,10 @@ const {
23
24
  getTriggerList,
24
25
  } = require("./common_lists");
25
26
 
27
+ const db = require("@saltcorn/data/db");
28
+ const { getState } = require("@saltcorn/data/db/state");
29
+ const { create_pack_from_tag } = require("@saltcorn/admin-models/models/pack");
30
+
26
31
  const router = new Router();
27
32
  module.exports = router;
28
33
 
@@ -42,7 +47,7 @@ router.get(
42
47
  mkTable(
43
48
  [
44
49
  {
45
- label: req.__("Tagname"),
50
+ label: req.__("Tag name"),
46
51
  key: (r) =>
47
52
  link(`/tag/${r.id || r.name}?show_list=tables`, text(r.name)),
48
53
  },
@@ -57,7 +62,7 @@ router.get(
57
62
  a(
58
63
  {
59
64
  href: `/tag/new`,
60
- class: "btn btn-primary",
65
+ class: "btn btn-primary mt-3",
61
66
  },
62
67
  req.__("Create tag")
63
68
  ),
@@ -73,6 +78,13 @@ router.get(
73
78
  error_catcher(async (req, res) => {
74
79
  res.sendWrap(req.__(`New tag`), {
75
80
  above: [
81
+ {
82
+ type: "breadcrumbs",
83
+ crumbs: [
84
+ { text: req.__(`Tags`), href: "/tag" },
85
+ { text: req.__(`New`) },
86
+ ],
87
+ },
76
88
  {
77
89
  type: "card",
78
90
  title: req.__(`New tag`),
@@ -97,7 +109,26 @@ router.get(
97
109
  })
98
110
  );
99
111
 
100
- const headerWithCollapser = (title, cardId, showList) =>
112
+ router.get(
113
+ "/download-pack/:idorname",
114
+ isAdmin,
115
+ error_catcher(async (req, res) => {
116
+ const { idorname } = req.params;
117
+ const id = parseInt(idorname);
118
+ const tag = await Tag.findOne(id ? { id } : { name: idorname });
119
+ if (!tag) {
120
+ req.flash("error", req.__("Tag not found"));
121
+ return res.redirect(`/tag`);
122
+ }
123
+ const pack = await create_pack_from_tag(tag);
124
+ const readStream = new stream.PassThrough();
125
+ readStream.end(JSON.stringify(pack));
126
+ res.type("application/json");
127
+ res.attachment(`${tag.name}-pack.json`);
128
+ readStream.pipe(res);
129
+ })
130
+ );
131
+ const headerWithCollapser = (title, cardId, showList, count) =>
101
132
  a(
102
133
  {
103
134
  class: `card-header-left-collapse ${!showList ? "collapsed" : ""} ps-3`,
@@ -107,7 +138,8 @@ const headerWithCollapser = (title, cardId, showList) =>
107
138
  "aria-controls": cardId,
108
139
  role: "button",
109
140
  },
110
- title
141
+ title,
142
+ ` (${count})`
111
143
  );
112
144
 
113
145
  const isShowList = (showList, listType) => showList === listType;
@@ -139,14 +171,15 @@ router.get(
139
171
  above: [
140
172
  {
141
173
  type: "breadcrumbs",
142
- crumbs: [{ text: req.__(`Tag: %s`, tag.name) }],
174
+ crumbs: [{ text: req.__(`Tags`), href: "/tag" }, { text: tag.name }],
143
175
  },
144
176
  {
145
177
  type: "card",
146
178
  title: headerWithCollapser(
147
179
  req.__("Tables"),
148
180
  tablesDomId,
149
- isShowList(show_list, "tables")
181
+ isShowList(show_list, "tables"),
182
+ tables.length
150
183
  ),
151
184
  contents: [
152
185
  await tablesList(tables, req, {
@@ -168,7 +201,8 @@ router.get(
168
201
  title: headerWithCollapser(
169
202
  req.__("Views"),
170
203
  viewsDomId,
171
- isShowList(show_list, "views")
204
+ isShowList(show_list, "views"),
205
+ views.length
172
206
  ),
173
207
  contents: [
174
208
  await viewsList(views, req, {
@@ -190,10 +224,11 @@ router.get(
190
224
  title: headerWithCollapser(
191
225
  req.__("Pages"),
192
226
  pagesDomId,
193
- isShowList(show_list, "pages")
227
+ isShowList(show_list, "pages"),
228
+ pages.length
194
229
  ),
195
230
  contents: [
196
- getPageList(pages, roles, req, {
231
+ await getPageList(pages, roles, req, {
197
232
  tagId: tag.id,
198
233
  domId: pagesDomId,
199
234
  showList: isShowList(show_list, "pages"),
@@ -213,10 +248,11 @@ router.get(
213
248
  title: headerWithCollapser(
214
249
  req.__("Triggers"),
215
250
  triggersDomId,
216
- isShowList(show_list, "triggers")
251
+ isShowList(show_list, "triggers"),
252
+ triggers.length
217
253
  ),
218
254
  contents: [
219
- getTriggerList(triggers, req, {
255
+ await getTriggerList(triggers, req, {
220
256
  tagId: tag.id,
221
257
  domId: triggersDomId,
222
258
  showList: isShowList(show_list, "triggers"),
@@ -230,6 +266,19 @@ router.get(
230
266
  ),
231
267
  ],
232
268
  },
269
+ {
270
+ type: "card",
271
+ contents: [
272
+ a(
273
+ {
274
+ class: "btn btn-outline-primary",
275
+ href: `/tag/download-pack/${tag.id}`,
276
+ },
277
+ i({ class: "fas fa-download me-2" }),
278
+ "Download pack"
279
+ ),
280
+ ],
281
+ },
233
282
  ],
234
283
  });
235
284
  })
package/routes/view.js CHANGED
@@ -71,10 +71,14 @@ router.get(
71
71
  const isModal = req.headers?.saltcornmodalrequest;
72
72
 
73
73
  const contents0 = await view.run_possibly_on_page(query, req, res);
74
- const title =
74
+ let title =
75
75
  isModal && view.attributes?.popup_title
76
76
  ? view.attributes?.popup_title
77
- : scan_for_page_title(contents0, view.name);
77
+ : view.attributes?.page_title ||
78
+ scan_for_page_title(contents0, view.name); //legacy
79
+ if ((title || "").includes("{{")) {
80
+ title = await view.interpolate_title_string(title, query);
81
+ }
78
82
  if (isModal && view.attributes?.popup_width)
79
83
  res.set(
80
84
  "SaltcornModalWidth",
@@ -82,10 +86,24 @@ router.get(
82
86
  view.attributes?.popup_width_units || "px"
83
87
  }`
84
88
  );
89
+ if (isModal && view.attributes?.popup_minwidth)
90
+ res.set(
91
+ "SaltcornModalMinWidth",
92
+ `${view.attributes?.popup_minwidth}${
93
+ view.attributes?.popup_minwidth_units || "px"
94
+ }`
95
+ );
85
96
  if (isModal && view.attributes?.popup_save_indicator)
86
97
  res.set("SaltcornModalSaveIndicator", `true`);
87
98
  if (isModal && view.attributes?.popup_link_out)
88
99
  res.set("SaltcornModalLinkOut", `true`);
100
+ if (view.attributes?.page_description) {
101
+ let description = view.attributes?.page_description;
102
+ if ((description || "").includes("{{")) {
103
+ description = await view.interpolate_title_string(description, query);
104
+ }
105
+ title = { title, description };
106
+ }
89
107
  const tock = new Date();
90
108
  const ms = tock.getTime() - tic.getTime();
91
109
  if (!isTest())
@@ -29,6 +29,9 @@ const Workflow = require("@saltcorn/data/models/workflow");
29
29
  const User = require("@saltcorn/data/models/user");
30
30
  const Page = require("@saltcorn/data/models/page");
31
31
  const File = require("@saltcorn/data/models/file");
32
+ const Tag = require("@saltcorn/data/models/tag");
33
+ const TagEntry = require("@saltcorn/data/models/tag_entry");
34
+
32
35
  const db = require("@saltcorn/data/db");
33
36
  const { sleep } = require("@saltcorn/data/utils");
34
37
 
@@ -56,7 +59,18 @@ router.get(
56
59
  error_catcher(async (req, res) => {
57
60
  let orderBy = "name";
58
61
  if (req.query._sortby === "viewtemplate") orderBy = "viewtemplate";
59
- const views = await View.find({}, { orderBy, nocase: true });
62
+ const viewq = {};
63
+ let filterOnTag;
64
+ if (req.query._tag) {
65
+ const tagEntries = await TagEntry.find({
66
+ tag_id: +req.query._tag,
67
+ not: { view_id: null },
68
+ });
69
+ viewq.id = { in: tagEntries.map((te) => te.view_id).filter(Boolean) };
70
+ filterOnTag = await Tag.findOne({ id: +req.query._tag });
71
+ }
72
+
73
+ const views = await View.find(viewq, { orderBy, nocase: true });
60
74
  await setTableRefs(views);
61
75
 
62
76
  if (req.query._sortby === "table")
@@ -64,7 +78,7 @@ router.get(
64
78
  a.table.toLowerCase() > b.table.toLowerCase() ? 1 : -1
65
79
  );
66
80
 
67
- const viewMarkup = await viewsList(views, req);
81
+ const viewMarkup = await viewsList(views, req, { filterOnTag });
68
82
  const tables = await Table.find();
69
83
 
70
84
  res.sendWrap(req.__(`Views`), {
@@ -182,6 +196,26 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
182
196
  required: true,
183
197
  options: roles.map((r) => ({ value: r.id, label: r.role })),
184
198
  }),
199
+ new Field({
200
+ name: "page_title",
201
+ label: req.__("Page title"),
202
+ type: "String",
203
+ parent_field: "attributes",
204
+ tab: "View settings",
205
+ sublabel: req.__(
206
+ "Some view patterns accept interpolations. Ex: <code>{{ name }}</code> or <code>{{ row ? `Edit ${row.name}` : `New person` }}</code>"
207
+ ),
208
+ }),
209
+ new Field({
210
+ name: "page_description",
211
+ label: req.__("Page description"),
212
+ type: "String",
213
+ parent_field: "attributes",
214
+ tab: "View settings",
215
+ sublabel: req.__(
216
+ "For search engines. Some view patterns accept interpolations."
217
+ ),
218
+ }),
185
219
  new Field({
186
220
  name: "default_render_page",
187
221
  label: req.__("Show on page"),
@@ -215,6 +249,8 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
215
249
  type: "String",
216
250
  parent_field: "attributes",
217
251
  tab: "Popup settings",
252
+ sublabel:
253
+ "Some view patterns accept interpolations. Ex: <code>{{ name }}</code> or <code>{{ row ? `Edit ${row.name}` : `New person` }}</code>",
218
254
  }),
219
255
  {
220
256
  name: "popup_width",
@@ -236,6 +272,26 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
236
272
  options: ["px", "%", "vw", "em", "rem"],
237
273
  },
238
274
  },
275
+ {
276
+ name: "popup_minwidth",
277
+ label: req.__("Popup min width"),
278
+ type: "Integer",
279
+ tab: "Popup settings",
280
+ parent_field: "attributes",
281
+ attributes: { asideNext: true },
282
+ },
283
+ {
284
+ name: "popup_minwidth_units",
285
+ label: req.__("Units"),
286
+ type: "String",
287
+ tab: "Popup settings",
288
+ fieldview: "radio_group",
289
+ parent_field: "attributes",
290
+ attributes: {
291
+ inline: true,
292
+ options: ["px", "%", "vw", "em", "rem"],
293
+ },
294
+ },
239
295
  {
240
296
  name: "popup_save_indicator",
241
297
  label: req.__("Save indicator"),
@@ -569,7 +625,10 @@ const respondWorkflow = (view, wf, wfres, req, res, table) => {
569
625
  else if (wfres.renderBuilder) {
570
626
  wfres.renderBuilder.options.view_id = view.id;
571
627
  res.sendWrap(
572
- req.__(`%s configuration`, view.name),
628
+ {
629
+ title: req.__(`%s configuration`, view.name),
630
+ requestFluidLayout: true,
631
+ },
573
632
  wrap(renderBuilder(wfres.renderBuilder, req.csrfToken()), true)
574
633
  );
575
634
  } else res.redirect(wfres.redirect);
@@ -757,7 +816,7 @@ router.post(
757
816
  await View.update({ configuration: newcfg }, +id);
758
817
  res.json({ success: "ok" });
759
818
  } else {
760
- res.json({ error: "no view" });
819
+ res.json({ error: req.__("Unable to save: No view") });
761
820
  }
762
821
  })
763
822
  );
@@ -419,6 +419,10 @@ describe("update matching rows", () => {
419
419
  await field.update({ is_unique: false });
420
420
  });
421
421
 
422
+ afterAll(async () => {
423
+ await resetToFixtures();
424
+ });
425
+
422
426
  it("update matching books normal", async () => {
423
427
  const table = Table.findOne({ name: "books" });
424
428
  await updateMatchingRows({
@@ -755,7 +759,7 @@ describe("relation path to query and state", () => {
755
759
  .expect(toNotInclude("album B"));
756
760
  });
757
761
 
758
- it("OneToOneSHow", async () => {
762
+ it("OneToOneShow", async () => {
759
763
  const app = await getApp({ disableCsrf: true });
760
764
  const loginCookie = await getAdminLoginCookie();
761
765
  await request(app)
@@ -805,7 +809,7 @@ describe("relation path to query and state", () => {
805
809
  .expect(toInclude(`value="artist B"`));
806
810
  });
807
811
 
808
- it("Parent", async () => {
812
+ it("Parent one layer", async () => {
809
813
  const app = await getApp({ disableCsrf: true });
810
814
  const loginCookie = await getAdminLoginCookie();
811
815
  await request(app)
@@ -833,6 +837,28 @@ describe("relation path to query and state", () => {
833
837
  .expect(toInclude("No row selected"));
834
838
  });
835
839
 
840
+ it("Parent two layers", async () => {
841
+ const app = await getApp({ disableCsrf: true });
842
+ const loginCookie = await getAdminLoginCookie();
843
+ await request(app)
844
+ .get(`/view/show_patient_with_publisher?id=2`)
845
+ .set("Cookie", loginCookie)
846
+ // view link
847
+ .expect(toInclude("/view/show_publisher?.patients.favbook.publisher=2"))
848
+ // embedded show
849
+ .expect(toInclude("Michael Douglas"))
850
+ .expect(toInclude("AK Press"));
851
+
852
+ await request(app)
853
+ .get(`/view/show_patient_with_publisher?id=1`)
854
+ .set("Cookie", loginCookie)
855
+ // view link
856
+ .expect(toInclude("/view/show_publisher?.patients.favbook.publisher=1"))
857
+ // embedded show
858
+ .expect(toInclude("Kirk Douglas"))
859
+ .expect(toInclude("No row selected"));
860
+ });
861
+
836
862
  it("RelationPath", async () => {
837
863
  const app = await getApp({ disableCsrf: true });
838
864
  const loginCookie = await getAdminLoginCookie();
@@ -873,11 +899,23 @@ describe("edit-in-edit with relation path and legacy", () => {
873
899
  const app = await getApp({ disableCsrf: true });
874
900
  const loginCookie = await getAdminLoginCookie();
875
901
  await request(app)
876
- .get("/view/edit_department_with_edit_in_edit_legacy?id=1")
902
+ .get("/view/edit_department_with_edit_in_edit_relation_path?id=1")
877
903
  .set("Cookie", loginCookie)
878
904
  .expect(toInclude("add_repeater"));
879
-
880
- // TODO post
905
+ await request(app)
906
+ .post("/view/edit_department_with_edit_in_edit_relation_path?id=1")
907
+ .set("Cookie", loginCookie)
908
+ .send({
909
+ department_0: "1",
910
+ department_1: "1",
911
+ id: "1",
912
+ id_0: "1",
913
+ id_1: "2",
914
+ name: "my_department",
915
+ name_0: "manager",
916
+ name_1: "my_employee",
917
+ })
918
+ .expect(toRedirect("/"));
881
919
  });
882
920
 
883
921
  it("edit-in-edit with relation path two layer", async () => {
@@ -887,10 +925,21 @@ describe("edit-in-edit with relation path and legacy", () => {
887
925
  .get("/view/edit_cover_with_edit_artist_on_album_rel_path?id=1")
888
926
  .set("Cookie", loginCookie)
889
927
  .expect(toInclude("add_repeater"));
890
-
891
- // TODO post
928
+ await request(app)
929
+ .post("/view/edit_cover_with_edit_artist_on_album_rel_path?id=1")
930
+ .set("Cookie", loginCookie)
931
+ .send({
932
+ album_0: "1",
933
+ album_1: "1",
934
+ artist_0: "1",
935
+ artist_1: "2",
936
+ id: "1",
937
+ id_0: "1",
938
+ id_1: "3",
939
+ name: "green cover",
940
+ })
941
+ .expect(toRedirect("/"));
892
942
  });
893
-
894
943
  it("edit-in-edit legacy one layer", async () => {
895
944
  const app = await getApp({ disableCsrf: true });
896
945
  const loginCookie = await getAdminLoginCookie();
@@ -898,8 +947,20 @@ describe("edit-in-edit with relation path and legacy", () => {
898
947
  .get("/view/edit_department_with_edit_in_edit_legacy?id=1")
899
948
  .set("Cookie", loginCookie)
900
949
  .expect(toInclude("add_repeater"));
901
-
902
- // TODO post
950
+ await request(app)
951
+ .post("/view/edit_department_with_edit_in_edit_legacy?id=1")
952
+ .set("Cookie", loginCookie)
953
+ .send({
954
+ department_0: "1",
955
+ department_1: "1",
956
+ id: "1",
957
+ id_0: "1",
958
+ id_1: "2",
959
+ name: "my_department",
960
+ name_0: "manager",
961
+ name_1: "my_employee",
962
+ })
963
+ .expect(toRedirect("/"));
903
964
  });
904
965
 
905
966
  it("edit-in-edit with relation path two layer", async () => {
@@ -909,8 +970,20 @@ describe("edit-in-edit with relation path and legacy", () => {
909
970
  .get("/view/edit_cover_with_edit_artist_on_album_rel_path?id=1")
910
971
  .set("Cookie", loginCookie)
911
972
  .expect(toInclude("add_repeater"));
912
-
913
- // TODO post
973
+ await request(app)
974
+ .post("/view/edit_cover_with_edit_artist_on_album_rel_path?id=1")
975
+ .set("Cookie", loginCookie)
976
+ .send({
977
+ album_0: "1",
978
+ album_1: "1",
979
+ artist_0: "1",
980
+ artist_1: "2",
981
+ id: "1",
982
+ id_0: "1",
983
+ id_1: "3",
984
+ name: "green cover",
985
+ })
986
+ .expect(toRedirect("/"));
914
987
  });
915
988
 
916
989
  it("edit-in-edit legacy two layer", async () => {
@@ -920,8 +993,20 @@ describe("edit-in-edit with relation path and legacy", () => {
920
993
  .get("/view/edit_cover_with_edit_artist_on_album_legacy?id=1")
921
994
  .set("Cookie", loginCookie)
922
995
  .expect(toInclude("add_repeater"));
923
-
924
- // TODO post
996
+ await request(app)
997
+ .post("/view/edit_cover_with_edit_artist_on_album_legacy?id=1")
998
+ .set("Cookie", loginCookie)
999
+ .send({
1000
+ album_0: "1",
1001
+ album_1: "1",
1002
+ artist_0: "1",
1003
+ artist_1: "2",
1004
+ id: "1",
1005
+ id_0: "1",
1006
+ id_1: "3",
1007
+ name: "green cover",
1008
+ })
1009
+ .expect(toRedirect("/"));
925
1010
  });
926
1011
  });
927
1012
 
@@ -982,6 +1067,21 @@ describe("legacy relations with relation path", () => {
982
1067
  await request(app)
983
1068
  .get("/view/authoredit_with_show?id=1")
984
1069
  .set("Cookie", loginCookie)
985
- .expect(toInclude(["Herman Melville", "agi"]));
1070
+ .expect(toInclude("Herman Melville"));
1071
+ });
1072
+
1073
+ it("edit-view with independent list", async () => {
1074
+ const app = await getApp({ disableCsrf: true });
1075
+ const loginCookie = await getAdminLoginCookie();
1076
+ await request(app)
1077
+ .get("/view/authoredit_with_independent_list")
1078
+ .set("Cookie", loginCookie)
1079
+ .expect(toInclude("Herman Melville"))
1080
+ .expect(toInclude("Delete"));
1081
+ await request(app)
1082
+ .get("/view/authoredit_with_independent_list?id=1")
1083
+ .set("Cookie", loginCookie)
1084
+ .expect(toInclude("Herman Melville"))
1085
+ .expect(toInclude("Delete"));
986
1086
  });
987
1087
  });
@@ -360,27 +360,6 @@ describe("viewedit new Show", () => {
360
360
  .send("columns=" + encodeURIComponent(JSON.stringify(columns)))
361
361
  .send("layout=" + encodeURIComponent(JSON.stringify(layout)))
362
362
  .set("Cookie", loginCookie)
363
- .expect(toInclude("Set page title"));
364
- });
365
- it("save new view page title", async () => {
366
- const loginCookie = await getAdminLoginCookie();
367
- const table = Table.findOne({ name: "books" });
368
-
369
- const ctx = encodeURIComponent(
370
- JSON.stringify({
371
- table_id: table.id,
372
- viewname: "mybook",
373
- layout,
374
- columns,
375
- })
376
- );
377
-
378
- const app = await getApp({ disableCsrf: true });
379
- await request(app)
380
- .post("/viewedit/config/mybook")
381
- .send("contextEnc=" + ctx)
382
- .send("stepName=Set+page+title")
383
- .set("Cookie", loginCookie)
384
363
  .expect(toRedirect("/viewedit"));
385
364
  });
386
365
  it("should show new view", async () => {