@saltcorn/builder 0.9.2 → 0.9.3-beta.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.
@@ -4,7 +4,7 @@
4
4
  * @subcategory components / elements
5
5
  */
6
6
 
7
- import React, { useContext } from "react";
7
+ import React, { useMemo } from "react";
8
8
  import { useNode } from "@craftjs/core";
9
9
  import optionsCtx from "../context";
10
10
  import {
@@ -16,10 +16,11 @@ import {
16
16
  setAPropGen,
17
17
  FormulaTooltip,
18
18
  HelpTopicLink,
19
+ prepCacheAndFinder,
19
20
  } from "./utils";
20
21
 
21
- import { RelationPicker } from "./RelationPicker";
22
22
  import { RelationBadges } from "./RelationBadges";
23
+ import { RelationOnDemandPicker } from "./RelationOnDemandPicker";
23
24
 
24
25
  export /**
25
26
  * @param {object} props
@@ -43,7 +44,6 @@ export /**
43
44
  const ViewLink = ({
44
45
  name,
45
46
  block,
46
- view_name,
47
47
  minRole,
48
48
  link_style,
49
49
  link_size,
@@ -61,8 +61,7 @@ const ViewLink = ({
61
61
  } = useNode((node) => ({ selected: node.events.selected }));
62
62
  const names = name.split(":");
63
63
 
64
- const displabel =
65
- label || view_name || (names.length > 1 ? names[1] : names[0]);
64
+ const displabel = label || (names.length > 1 ? names[1] : names[0]);
66
65
  return (
67
66
  <span
68
67
  className={`${textStyle} ${inModal ? "btn btn-secondary btn-sm" : ""} ${
@@ -86,7 +85,7 @@ const ViewLink = ({
86
85
  };
87
86
 
88
87
  export /**
89
- * @returns {div}
88
+ * @returns
90
89
  * @category saltcorn-builder
91
90
  * @subcategory components
92
91
  * @namespace
@@ -109,7 +108,6 @@ const ViewLinkSettings = () => {
109
108
  link_bordercol: node.data.props.link_bordercol,
110
109
  link_textcol: node.data.props.link_textcol,
111
110
  extra_state_fml: node.data.props.extra_state_fml,
112
- view_name: node.data.props.view_name,
113
111
  }));
114
112
  const {
115
113
  actions: { setProp },
@@ -122,10 +120,13 @@ const ViewLinkSettings = () => {
122
120
  inModal,
123
121
  textStyle,
124
122
  extra_state_fml,
125
- view_name,
126
123
  link_target_blank,
127
124
  } = node;
128
- const options = useContext(optionsCtx);
125
+ const options = React.useContext(optionsCtx);
126
+ const { caches, finder } = useMemo(
127
+ () => prepCacheAndFinder(options),
128
+ [undefined]
129
+ );
129
130
  let errorString = false;
130
131
  try {
131
132
  Function("return " + extra_state_fml);
@@ -135,18 +136,42 @@ const ViewLinkSettings = () => {
135
136
  const setAProp = setAPropGen(setProp);
136
137
  //legacy values
137
138
  const use_view_name =
138
- view_name ||
139
- (name &&
140
- ((names) => (names.length > 1 ? names[1] : names[0]))(name.split(":")));
139
+ name &&
140
+ ((names) => (names.length > 1 ? names[1] : names[0]))(name.split(":"));
141
+ const hasLegacyRelation = name && name.includes(":");
142
+ const safeViewName = use_view_name?.includes(".")
143
+ ? use_view_name.split(".")[0]
144
+ : use_view_name;
145
+ const [relations, setRelations] = React.useState(
146
+ finder.findRelations(
147
+ options.tableName,
148
+ safeViewName,
149
+ options.excluded_subview_templates
150
+ )
151
+ );
152
+ let safeRelation = relation;
153
+ if (!safeRelation && !hasLegacyRelation && relations?.paths.length > 0) {
154
+ safeRelation = relations.paths[0];
155
+ setProp((prop) => {
156
+ prop.relation = safeRelation;
157
+ });
158
+ }
141
159
  const set_view_name = (e) => {
142
160
  if (e.target) {
143
161
  const target_value = e.target.value;
144
- setProp((prop) => (prop.view_name = target_value));
145
162
  if (target_value !== use_view_name) {
146
- setProp((prop) => {
147
- prop.name = options.view_relation_opts[target_value][0].value;
148
- prop.relation = undefined;
149
- });
163
+ const newRelations = finder.findRelations(
164
+ options.tableName,
165
+ target_value,
166
+ options.excluded_subview_templates
167
+ );
168
+ if (newRelations.paths.length > 0) {
169
+ setProp((prop) => {
170
+ prop.name = target_value;
171
+ prop.relation = newRelations.paths[0];
172
+ });
173
+ setRelations(newRelations);
174
+ }
150
175
  }
151
176
  }
152
177
  };
@@ -165,7 +190,7 @@ const ViewLinkSettings = () => {
165
190
  onChange={set_view_name}
166
191
  onBlur={set_view_name}
167
192
  >
168
- {options.view_name_opts.map((f, ix) => (
193
+ {options.views.map((f, ix) => (
169
194
  <option key={ix} value={f.name}>
170
195
  {f.label}
171
196
  </option>
@@ -175,9 +200,8 @@ const ViewLinkSettings = () => {
175
200
  </tr>
176
201
  <tr>
177
202
  <td colSpan="2">
178
- <RelationPicker
179
- options={options}
180
- viewname={use_view_name}
203
+ <RelationOnDemandPicker
204
+ relations={relations.layers}
181
205
  update={(relPath) => {
182
206
  if (relPath.startsWith(".")) {
183
207
  setProp((prop) => {
@@ -194,9 +218,9 @@ const ViewLinkSettings = () => {
194
218
  />
195
219
  <RelationBadges
196
220
  view={name}
197
- relation={relation}
221
+ relation={safeRelation}
198
222
  parentTbl={options.tableName}
199
- fk_options={options.fk_options}
223
+ tableNameCache={caches.tableNameCache}
200
224
  />
201
225
  </td>
202
226
  </tr>
@@ -308,7 +332,6 @@ ViewLink.craft = {
308
332
  { name: "inModal", segment_name: "in_modal", column_name: "in_modal" },
309
333
  "minRole",
310
334
  "link_style",
311
- "view_name",
312
335
  "link_icon",
313
336
  "link_size",
314
337
  "link_target_blank",
@@ -4,7 +4,7 @@
4
4
  * @subcategory components / elements
5
5
  */
6
6
  /* globals $, _sc_globalCsrf*/
7
- import React, { Fragment, useContext, useState } from "react";
7
+ import React, { Fragment, useState } from "react";
8
8
  import optionsCtx from "../context";
9
9
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
10
10
  import {
@@ -80,7 +80,7 @@ export const BlockOrInlineSetting = ({ block, inline, textStyle, setProp }) =>
80
80
  );
81
81
 
82
82
  export const HelpTopicLink = ({ topic, ...context }) => {
83
- const { mode } = useContext(optionsCtx);
83
+ const { mode } = React.useContext(optionsCtx);
84
84
  let qs = "";
85
85
  Object.keys(context).forEach((k) => {
86
86
  qs += `${encodeURIComponent(k)}=${encodeURIComponent(context[k])}&`;
@@ -95,7 +95,7 @@ export const HelpTopicLink = ({ topic, ...context }) => {
95
95
  };
96
96
 
97
97
  export const FormulaTooltip = () => {
98
- const { fields } = useContext(optionsCtx);
98
+ const { fields } = React.useContext(optionsCtx);
99
99
  return (
100
100
  <Tooltip>
101
101
  <div>
@@ -137,7 +137,7 @@ export /**
137
137
  * @subcategory components / elements / utils
138
138
  */
139
139
  const OrFormula = ({ setProp, isFormula, node, nodekey, children }) => {
140
- const { mode } = useContext(optionsCtx);
140
+ const { mode } = React.useContext(optionsCtx);
141
141
 
142
142
  /**
143
143
  * @returns {void}
@@ -225,7 +225,7 @@ export /**
225
225
  * @subcategory components / elements / utils
226
226
  */
227
227
  const MinRoleSetting = ({ minRole, setProp }) => {
228
- const options = useContext(optionsCtx);
228
+ const options = React.useContext(optionsCtx);
229
229
  return (
230
230
  <div>
231
231
  <label>Minimum Role</label>
@@ -259,7 +259,7 @@ export /**
259
259
  * @subcategory components / elements / utils
260
260
  */
261
261
  const MinRoleSettingRow = ({ minRole, setProp }) => {
262
- const options = useContext(optionsCtx);
262
+ const options = React.useContext(optionsCtx);
263
263
  return (
264
264
  <tr>
265
265
  <td>
@@ -743,7 +743,7 @@ const ConfigField = ({
743
743
  * @param {object} v
744
744
  * @returns {void}
745
745
  */
746
- const options = useContext(optionsCtx);
746
+ const options = React.useContext(optionsCtx);
747
747
 
748
748
  const myOnChange = (v) => {
749
749
  setProp((prop) => {
@@ -1412,85 +1412,20 @@ const Tooltip = ({ children }) => {
1412
1412
  );
1413
1413
  };
1414
1414
 
1415
- const getFkTarget = (field, options) => {
1416
- const option = options.find((fk) => fk.name === field);
1417
- return option ? option.reftable_name : null;
1418
- };
1419
-
1420
- export const parseRelationPath = (path, fk_options) => {
1421
- const result = [];
1422
- const tokens = path.split(".");
1423
- if (tokens.length >= 3) {
1424
- let currentTbl = tokens[1];
1425
- for (const relation of tokens.slice(2)) {
1426
- if (relation.indexOf("$") > 0) {
1427
- const [inboundTbl, inboundKey] = relation.split("$");
1428
- result.push({ type: "Inbound", table: inboundTbl, key: inboundKey });
1429
- currentTbl = inboundTbl;
1430
- } else {
1431
- const targetTbl = getFkTarget(relation, fk_options[currentTbl]);
1432
- if (!targetTbl) {
1433
- console.log(`The foreign key '${relation}' is invalid`);
1434
- return [];
1435
- }
1436
- result.push({ type: "Foreign", table: targetTbl, key: relation });
1437
- currentTbl = targetTbl;
1438
- }
1439
- }
1440
- }
1441
- return result;
1442
- };
1443
-
1444
- export const parseLegacyRelation = (type, rest, parentTbl) => {
1445
- switch (type) {
1446
- case "ChildList": {
1447
- const path = rest ? rest.split(".") : [];
1448
- if (path.length === 3) {
1449
- const [viewName, table, key] = path;
1450
- return [
1451
- {
1452
- type: "Inbound",
1453
- table,
1454
- key,
1455
- },
1456
- ];
1457
- } else if (path.length === 5) {
1458
- const [viewName, thrTbl, thrTblFkey, fromTbl, fromTblFkey] = path;
1459
- return [
1460
- {
1461
- type: "Inbound",
1462
- table: thrTbl,
1463
- key: thrTblFkey,
1464
- },
1465
- {
1466
- type: "Inbound",
1467
- table: fromTbl,
1468
- key: fromTblFkey,
1469
- },
1470
- ];
1471
- }
1472
- break;
1473
- }
1474
- case "Independent": {
1475
- return [{ type: "Independent", table: "None (no relation)" }];
1476
- }
1477
- case "Own": {
1478
- return [{ type: "Own", table: `${parentTbl} (same table)` }];
1479
- }
1480
- case "OneToOneShow": {
1481
- const tokens = rest ? rest.split(".") : [];
1482
- if (tokens.length !== 3) break;
1483
- const [viewname, relatedTbl, fkey] = tokens;
1484
- return [{ type: "Inbound", table: relatedTbl, key: fkey }];
1485
- }
1486
- case "ParentShow": {
1487
- const tokens = rest ? rest.split(".") : [];
1488
- if (tokens.length !== 3) break;
1489
- const [viewname, parentTbl, fkey] = tokens;
1490
- return [{ type: "Foreign", table: parentTbl, key: fkey }];
1415
+ export const buildTableCaches = (allTables) => {
1416
+ const tableIdCache = {};
1417
+ const tableNameCache = {};
1418
+ const fieldCache = {};
1419
+ for (const table of allTables) {
1420
+ tableIdCache[table.id] = table;
1421
+ tableNameCache[table.name] = table;
1422
+ for (const field of table.foreign_keys) {
1423
+ if (!fieldCache[field.reftable_name])
1424
+ fieldCache[field.reftable_name] = [];
1425
+ fieldCache[field.reftable_name].push(field);
1491
1426
  }
1492
1427
  }
1493
- return [];
1428
+ return { tableIdCache, tableNameCache, fieldCache };
1494
1429
  };
1495
1430
 
1496
1431
  export const removeWhitespaces = (str) => {
@@ -1534,3 +1469,19 @@ export const buildBootstrapOptions = (values) => {
1534
1469
  </option>
1535
1470
  ));
1536
1471
  };
1472
+
1473
+ export const prepCacheAndFinder = ({
1474
+ tables,
1475
+ views,
1476
+ max_relations_layer_depth,
1477
+ }) => {
1478
+ if (tables && views) {
1479
+ const caches = buildTableCaches(tables);
1480
+ const finder = new relationHelpers.RelationsFinder(
1481
+ caches,
1482
+ views,
1483
+ max_relations_layer_depth || 6
1484
+ );
1485
+ return { caches, finder };
1486
+ } else return { caches: null, finder: null };
1487
+ };
@@ -0,0 +1,275 @@
1
+ import React from "react";
2
+ import renderer from "react-test-renderer";
3
+
4
+ import {
5
+ fixturesData,
6
+ withAnotherUserField,
7
+ withSecondTopicField,
8
+ withMultipleInbounds,
9
+ withKeyFromLayerTwo,
10
+ withKeyFromLayerThree,
11
+ withSimplePostTopicrelation,
12
+ } from "./test_data";
13
+ import { ViewSettings } from "../src/components/elements/View";
14
+ import { ViewLinkSettings } from "../src/components/elements/ViewLink";
15
+
16
+ jest.mock("@craftjs/core", () => ({
17
+ useNode: jest.fn(),
18
+ }));
19
+
20
+ const doTest = (
21
+ tables,
22
+ views,
23
+ tableName,
24
+ viewName,
25
+ expected,
26
+ excludedTemplates,
27
+ view,
28
+ relation
29
+ ) => {
30
+ // mock craftjs
31
+ const useNodeMock = jest.fn();
32
+ useNodeMock.mockReturnValue({
33
+ actions: {
34
+ setProp: (fn) => {},
35
+ },
36
+ relation: relation,
37
+ view: view || viewName,
38
+ name: view || viewName,
39
+ });
40
+ require("@craftjs/core").useNode.mockImplementation(useNodeMock);
41
+ // mock react context
42
+ const useContextMock = (React.useContext = jest.fn());
43
+ useContextMock.mockReturnValue({
44
+ tables: tables,
45
+ views: views,
46
+ tableName: tableName,
47
+ roles: [],
48
+ excluded_subview_templates: excludedTemplates,
49
+ });
50
+ // spy on useState and extract the relations (first call)
51
+ const spy = jest.spyOn(React, "useState");
52
+ renderer.create(<ViewSettings></ViewSettings>);
53
+ expect(spy).toBeCalled();
54
+ const vCallArgs = spy.mock.calls[0];
55
+ expect(vCallArgs[0].paths).toBeDefined();
56
+ expect(vCallArgs[0].paths).toHaveLength(expected.length);
57
+ expect(vCallArgs[0].paths).toEqual(expect.arrayContaining(expected));
58
+
59
+ renderer.create(<ViewLinkSettings></ViewLinkSettings>);
60
+ expect(spy.mock.calls).toHaveLength(4);
61
+ const vLinkcallArgs = spy.mock.calls[2];
62
+ expect(vLinkcallArgs[0].paths).toBeDefined();
63
+ expect(vLinkcallArgs[0].paths).toHaveLength(expected.length);
64
+ expect(vLinkcallArgs[0].paths).toEqual(expect.arrayContaining(expected));
65
+ };
66
+
67
+ describe("relations tests", () => {
68
+ beforeAll(() => {
69
+ // inject relationHelpers (normally it's a script tag)
70
+ global.relationHelpers = {
71
+ ...require("../../server/public/relation_helpers.js"),
72
+ };
73
+ });
74
+ beforeEach(() => {
75
+ jest.restoreAllMocks();
76
+ });
77
+ describe("single relations", () => {
78
+ it("parent relations", async () => {
79
+ const { tables, views } = fixturesData();
80
+ const expected = [".fan_club.artist"];
81
+ doTest(tables, views, "fan_club", "show_artist", expected);
82
+ });
83
+
84
+ it("one to one relations", async () => {
85
+ const { tables, views } = fixturesData();
86
+ const expected = [".covers.albums$cover"];
87
+ doTest(tables, views, "covers", "show_album", expected);
88
+ });
89
+
90
+ it("employee department relation", async () => {
91
+ const { tables, views } = fixturesData();
92
+ const expected = [".employee", ".employee.department.manager"];
93
+ doTest(tables, views, "employee", "show_manager", expected);
94
+ });
95
+ });
96
+
97
+ describe("multi relations", () => {
98
+ describe("inbound relations", () => {
99
+ const expectedOne = [
100
+ ".",
101
+ ".users.user_interested_in_topic$user.topic.blog_in_topic$topic",
102
+ ".users.user_interested_in_topic$user.topic.inbound_inbound$topic.bp_inbound.post.blog_in_topic$post",
103
+ ".users.messages$user.room.participants$room.user.user_interested_in_topic$user.topic.blog_in_topic$topic",
104
+ ];
105
+ it("single keys to source and rel table", async () => {
106
+ const { tables, views } = fixturesData();
107
+ doTest(tables, views, "users", "blog_in_topic_feed", expectedOne);
108
+ });
109
+
110
+ const expectedTwo = [
111
+ ...expectedOne,
112
+ ".users.user_interested_in_topic$another_user.topic.blog_in_topic$topic",
113
+ ".users.user_interested_in_topic$another_user.topic.inbound_inbound$topic.bp_inbound.post.blog_in_topic$post",
114
+ ".users.messages$user.room.participants$room.user.user_interested_in_topic$another_user.topic.blog_in_topic$topic",
115
+ ];
116
+ it("multiple keys to source and single key to rel table", async () => {
117
+ const { tables, views } = withAnotherUserField();
118
+ doTest(tables, views, "users", "blog_in_topic_feed", expectedTwo);
119
+ });
120
+
121
+ const expectedThree = [
122
+ ...expectedTwo,
123
+ ".users.user_interested_in_topic$user.topic.blog_in_topic$second_topic",
124
+ ".users.user_interested_in_topic$another_user.topic.blog_in_topic$second_topic",
125
+ ".users.messages$user.room.participants$room.user.user_interested_in_topic$user.topic.blog_in_topic$second_topic",
126
+ ".users.messages$user.room.participants$room.user.user_interested_in_topic$another_user.topic.blog_in_topic$second_topic",
127
+ ];
128
+ it("multiple keys to source and rel table", async () => {
129
+ const { tables, views } = withSecondTopicField();
130
+ doTest(tables, views, "users", "blog_in_topic_feed", expectedThree);
131
+ });
132
+
133
+ const expectedFour = [
134
+ ...expectedThree,
135
+ ".users.user_interested_in_topic$user.another_user.second_inbound$user.topic.blog_in_topic$topic",
136
+ ".users.user_interested_in_topic$user.another_user.second_inbound$user.topic.blog_in_topic$second_topic",
137
+ ".users.second_inbound$user.topic.blog_in_topic$topic",
138
+ ".users.second_inbound$user.topic.blog_in_topic$second_topic",
139
+ ".users.second_inbound$user.topic.inbound_inbound$topic.bp_inbound.post.blog_in_topic$post",
140
+ ".users.messages$user.room.participants$room.user.second_inbound$user.topic.blog_in_topic$topic",
141
+ ".users.messages$user.room.participants$room.user.second_inbound$user.topic.blog_in_topic$second_topic",
142
+ ];
143
+ it("multiple inbound tables", async () => {
144
+ const { tables, views } = withMultipleInbounds();
145
+ doTest(tables, views, "users", "blog_in_topic_feed", expectedFour);
146
+ });
147
+
148
+ const expectedFive = [
149
+ ...expectedFour,
150
+ ".users.user_interested_in_topic$user.topic.inbound_inbound$topic.post_from_layer_two.blog_in_topic$post",
151
+ ".users.user_interested_in_topic$another_user.topic.inbound_inbound$topic.post_from_layer_two.blog_in_topic$post",
152
+ ".users.second_inbound$user.topic.inbound_inbound$topic.post_from_layer_two.blog_in_topic$post",
153
+ ".users.user_interested_in_topic$user.topic.blog_in_topic$topic.post.inbound_inbound$post_from_layer_two.topic.blog_in_topic$second_topic",
154
+ ".users.user_interested_in_topic$user.another_user.second_inbound$user.topic.inbound_inbound$topic.post_from_layer_two.blog_in_topic$post",
155
+ ".users.user_interested_in_topic$another_user.topic.blog_in_topic$topic.post.inbound_inbound$post_from_layer_two.topic.blog_in_topic$second_topic",
156
+ ".users.second_inbound$user.topic.blog_in_topic$topic.post.inbound_inbound$post_from_layer_two.topic.blog_in_topic$second_topic",
157
+ ];
158
+ it("key to source from layer two", async () => {
159
+ const { tables, views } = withKeyFromLayerTwo();
160
+ doTest(tables, views, "users", "blog_in_topic_feed", expectedFive);
161
+ });
162
+
163
+ const expectedSix = [
164
+ ...expectedFive,
165
+ ".users.user_interested_in_topic$user.topic.inbound_level_three$topic.inbound_level_two.bp_inbound.post.blog_in_topic$post",
166
+ ".users.user_interested_in_topic$user.topic.inbound_level_three$topic.inbound_level_two.post_from_layer_two.blog_in_topic$post",
167
+ ".users.user_interested_in_topic$another_user.topic.inbound_level_three$topic.inbound_level_two.bp_inbound.post.blog_in_topic$post",
168
+ ".users.user_interested_in_topic$another_user.topic.inbound_level_three$topic.inbound_level_two.post_from_layer_two.blog_in_topic$post",
169
+ ".users.second_inbound$user.topic.inbound_level_three$topic.inbound_level_two.bp_inbound.post.blog_in_topic$post",
170
+ ".users.second_inbound$user.topic.inbound_level_three$topic.inbound_level_two.post_from_layer_two.blog_in_topic$post",
171
+ ];
172
+ it("three levels inbound", async () => {
173
+ const { tables, views } = withKeyFromLayerThree();
174
+ doTest(tables, views, "users", "blog_in_topic_feed", expectedSix);
175
+ });
176
+
177
+ it("simple post topic relation", async () => {
178
+ const expected = [
179
+ ".",
180
+ ".users.favsimpletopic.simple_posts$topic",
181
+ ".users.favsimpletopic.simple_post_inbound$topic.post",
182
+ ".users.messages$user.room.participants$room.user.favsimpletopic.simple_posts$topic",
183
+ ".users.messages$user.room.participants$room.user.favsimpletopic.simple_post_inbound$topic.post",
184
+ ];
185
+ const { tables, views } = withSimplePostTopicrelation();
186
+ doTest(tables, views, "users", "simple_posts_list", expected);
187
+ });
188
+ });
189
+
190
+ describe("many to many relations", () => {
191
+ it("artist_plays_on_album", async () => {
192
+ const { tables, views } = fixturesData();
193
+ const expected = [".", ".artists.artist_plays_on_album$artist.album"];
194
+ doTest(tables, views, "artists", "albums_feed", expected);
195
+ });
196
+
197
+ it("tracks on album", async () => {
198
+ const { tables, views } = fixturesData();
199
+ const expected = [
200
+ ".",
201
+ ".artists.artist_plays_on_album$artist.album.tracks_on_album$album",
202
+ ];
203
+ doTest(tables, views, "artists", "tracks_on_album_feed", expected);
204
+ });
205
+
206
+ it("show pressing_job with embedded fan club feed", async () => {
207
+ const { tables, views } = fixturesData();
208
+ const expected = [
209
+ ".",
210
+ ".pressing_job.album.artist_plays_on_album$album.artist.fan_club$artist",
211
+ ];
212
+ doTest(tables, views, "pressing_job", "fan_club_feed", expected);
213
+ });
214
+ });
215
+
216
+ describe("excluded viewtemplates", () => {
217
+ it("excluded viewtemplates", async () => {
218
+ const { tables, views } = fixturesData();
219
+ const expected = [];
220
+ const excluded = ["Room"];
221
+ doTest(tables, views, "participants", "rooms_view", expected, excluded);
222
+ });
223
+ });
224
+
225
+ describe("open legacy relations", () => {
226
+ it("ChildList", async () => {
227
+ const { tables, views } = fixturesData();
228
+ const expected = [".", ".books.discusses_books$book"];
229
+ doTest(
230
+ tables,
231
+ views,
232
+ "books",
233
+ "disc_books_list",
234
+ expected,
235
+ [],
236
+ "ChildList:disc_books_list.discusses_books.book",
237
+ undefined
238
+ );
239
+ });
240
+
241
+ it("Independent", async () => {
242
+ const { tables, views } = fixturesData();
243
+ const expected = [
244
+ ".",
245
+ ".blog_posts.blog_in_topic$post.topic.inbound_inbound$topic.bp_inbound.post",
246
+ ];
247
+ doTest(
248
+ tables,
249
+ views,
250
+ "blog_posts",
251
+ "blog_posts_feed",
252
+ expected,
253
+ [],
254
+ "Independent:blog_posts_feed",
255
+ undefined
256
+ );
257
+ });
258
+
259
+ it("Own", async () => {
260
+ const { tables, views } = fixturesData();
261
+ const expected = [".books"];
262
+ doTest(
263
+ tables,
264
+ views,
265
+ "books",
266
+ "authorshow",
267
+ expected,
268
+ [],
269
+ "Own:authorshow",
270
+ undefined
271
+ );
272
+ });
273
+ });
274
+ });
275
+ });