jqtree 1.8.0 → 1.8.1

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 (60) hide show
  1. package/.eslintrc +13 -3
  2. package/.github/workflows/ci.yml +6 -6
  3. package/.github/workflows/codeql-analysis.yml +4 -4
  4. package/.github/workflows/size.yml +3 -3
  5. package/.github/workflows/static.yml +1 -1
  6. package/bower.json +1 -1
  7. package/config/jest.config.js +4 -0
  8. package/config/jest.polyfills.js +14 -0
  9. package/devserver/test_index.html +9 -0
  10. package/docs/.ruby-version +1 -1
  11. package/docs/_config.yml +1 -1
  12. package/docs/_entries/general/changelog.md +4 -0
  13. package/docs/_entries/multiple_selection/get-selected-nodes.md +1 -1
  14. package/docs/_entries/node/getnextnode.md +3 -6
  15. package/docs/_entries/node/getnextsibling.md +1 -1
  16. package/docs/_entries/node/getnextvisiblenode.md +8 -5
  17. package/docs/_entries/node/getpreviousnode.md +12 -0
  18. package/docs/_entries/node/getprevioussibling.md +1 -1
  19. package/docs/_entries/node/getpreviousvisiblenode.md +6 -5
  20. package/package.json +32 -30
  21. package/src/dataLoader.ts +19 -21
  22. package/src/dragAndDropHandler/dragElement.ts +37 -25
  23. package/src/dragAndDropHandler/generateHitAreas.ts +176 -0
  24. package/src/dragAndDropHandler/index.ts +32 -48
  25. package/src/dragAndDropHandler/iterateVisibleNodes.ts +91 -0
  26. package/src/dragAndDropHandler/types.ts +2 -1
  27. package/src/mouseHandler.ts +385 -0
  28. package/src/mouseUtils.ts +23 -0
  29. package/src/node.ts +1 -29
  30. package/src/nodeElement/folderElement.ts +1 -1
  31. package/src/nodeElement/ghostDropHint.ts +2 -1
  32. package/src/nodeElement/index.ts +2 -1
  33. package/src/playwright/coverage.ts +3 -3
  34. package/src/playwright/playwright.test.ts +150 -49
  35. package/src/playwright/testUtils.ts +28 -5
  36. package/src/position.ts +28 -0
  37. package/src/scrollHandler/containerScrollParent.ts +13 -23
  38. package/src/scrollHandler/createScrollParent.ts +22 -22
  39. package/src/scrollHandler/documentScrollParent.ts +16 -13
  40. package/src/scrollHandler.ts +6 -14
  41. package/src/test/jqTree/events.test.ts +97 -30
  42. package/src/test/jqTree/loadOnDemand.test.ts +22 -15
  43. package/src/test/jqTree/methods.test.ts +8 -11
  44. package/src/test/jqTree/mouse.test.ts +82 -0
  45. package/src/test/jqTree/options.test.ts +9 -8
  46. package/src/test/node.test.ts +2 -1
  47. package/src/test/{nodeUtil.test.ts → position.test.ts} +1 -1
  48. package/src/tree.jquery.ts +108 -184
  49. package/src/util.ts +10 -1
  50. package/src/version.ts +1 -1
  51. package/tree.jquery.debug.js +2158 -2135
  52. package/tree.jquery.debug.js.map +1 -1
  53. package/tree.jquery.js +3 -3
  54. package/tree.jquery.js.map +1 -1
  55. package/tsconfig.json +5 -3
  56. package/docs/_entries/functions/get-selected-nodes.md +0 -10
  57. package/src/dragAndDropHandler/hitAreasGenerator.ts +0 -175
  58. package/src/dragAndDropHandler/visibleNodeIterator.ts +0 -97
  59. package/src/mouse.widget.ts +0 -266
  60. package/src/mouseWidgetTypes.ts +0 -6
@@ -1,31 +1,24 @@
1
1
  import getGiven from "givens";
2
- import { rest } from "msw";
2
+ import { http, HttpResponse } from "msw";
3
3
  import { setupServer } from "msw/node";
4
4
  import { waitFor } from "@testing-library/dom";
5
+ import { userEvent } from "@testing-library/user-event";
5
6
  import "../../tree.jquery";
6
7
  import exampleData from "../support/exampleData";
7
8
  import { titleSpan } from "../support/testUtil";
8
9
 
9
10
  const context = describe;
10
11
 
11
- const server = setupServer();
12
-
13
- beforeAll(() => server.listen());
14
-
15
12
  beforeEach(() => {
16
13
  $("body").append('<div id="tree1"></div>');
17
14
  });
18
15
 
19
16
  afterEach(() => {
20
- server.resetHandlers();
21
-
22
17
  const $tree = $("#tree1");
23
18
  $tree.tree("destroy");
24
19
  $tree.remove();
25
20
  });
26
21
 
27
- afterAll(() => server.close());
28
-
29
22
  describe("tree.click", () => {
30
23
  interface Vars {
31
24
  node1: INode;
@@ -42,13 +35,13 @@ describe("tree.click", () => {
42
35
  given.$tree.tree({ data: exampleData });
43
36
  });
44
37
 
45
- it("fires tree.click", () => {
38
+ it("fires tree.click", async () => {
46
39
  const onClick = jest.fn();
47
40
  given.$tree.on("tree.click", onClick);
48
41
 
49
- given.titleSpan.trigger("click");
42
+ await userEvent.click(given.titleSpan.get(0) as HTMLElement);
50
43
  expect(onClick).toHaveBeenCalledWith(
51
- expect.objectContaining({ node: given.node1 })
44
+ expect.objectContaining({ node: given.node1 }),
52
45
  );
53
46
  });
54
47
  });
@@ -69,13 +62,16 @@ describe("tree.contextmenu", () => {
69
62
  given.$tree.tree({ data: exampleData });
70
63
  });
71
64
 
72
- it("fires tree.contextmenu", () => {
65
+ it("fires tree.contextmenu", async () => {
73
66
  const onContextMenu = jest.fn();
74
67
  given.$tree.on("tree.contextmenu", onContextMenu);
75
68
 
76
- given.titleSpan.trigger("contextmenu");
69
+ await userEvent.pointer({
70
+ target: given.titleSpan.get(0) as HTMLElement,
71
+ keys: "[MouseRight]",
72
+ });
77
73
  expect(onContextMenu).toHaveBeenCalledWith(
78
- expect.objectContaining({ node: given.node1 })
74
+ expect.objectContaining({ node: given.node1 }),
79
75
  );
80
76
  });
81
77
  });
@@ -96,13 +92,13 @@ describe("tree.dblclick", () => {
96
92
  given.$tree.tree({ data: exampleData });
97
93
  });
98
94
 
99
- it("fires tree.dblclick", () => {
95
+ it("fires tree.dblclick", async () => {
100
96
  const onDoubleClick = jest.fn();
101
97
  given.$tree.on("tree.dblclick", onDoubleClick);
102
98
 
103
- given.titleSpan.trigger("dblclick");
99
+ await userEvent.dblClick(given.titleSpan.get(0) as HTMLElement);
104
100
  expect(onDoubleClick).toHaveBeenCalledWith(
105
- expect.objectContaining({ node: given.node1 })
101
+ expect.objectContaining({ node: given.node1 }),
106
102
  );
107
103
  });
108
104
  });
@@ -129,12 +125,15 @@ describe("tree.init", () => {
129
125
  });
130
126
 
131
127
  context("with data loaded from an url", () => {
128
+ const server = setupServer(
129
+ http.get("/tree/", () => HttpResponse.json(exampleData)),
130
+ );
132
131
  beforeEach(() => {
133
- server.use(
134
- rest.get("/tree/", (_request, response, ctx) =>
135
- response(ctx.status(200), ctx.json(exampleData))
136
- )
137
- );
132
+ server.listen();
133
+ });
134
+
135
+ afterAll(() => {
136
+ server.close();
138
137
  });
139
138
 
140
139
  it("is called", async () => {
@@ -165,7 +164,7 @@ describe("tree.load_data", () => {
165
164
 
166
165
  given.$tree.tree({ data: exampleData });
167
166
  expect(onLoadData).toHaveBeenCalledWith(
168
- expect.objectContaining({ tree_data: exampleData })
167
+ expect.objectContaining({ tree_data: exampleData }),
169
168
  );
170
169
  });
171
170
  });
@@ -189,16 +188,16 @@ describe("tree.select", () => {
189
188
  });
190
189
  });
191
190
 
192
- it("fires tree.select", () => {
191
+ it("fires tree.select", async () => {
193
192
  const onSelect = jest.fn();
194
193
  given.$tree.on("tree.select", onSelect);
195
194
 
196
- given.titleSpan.trigger("click");
195
+ await userEvent.click(given.titleSpan.get(0) as HTMLElement);
197
196
  expect(onSelect).toHaveBeenCalledWith(
198
197
  expect.objectContaining({
199
198
  node: given.node1,
200
199
  deselected_node: null,
201
- })
200
+ }),
202
201
  );
203
202
  });
204
203
 
@@ -207,17 +206,85 @@ describe("tree.select", () => {
207
206
  given.$tree.tree("selectNode", given.node1);
208
207
  });
209
208
 
210
- it("fires tree.select with node is null", () => {
209
+ it("fires tree.select with node is null", async () => {
211
210
  const onSelect = jest.fn();
212
211
  given.$tree.on("tree.select", onSelect);
213
212
 
214
- given.titleSpan.trigger("click");
213
+ await userEvent.click(given.titleSpan.get(0) as HTMLElement);
215
214
  expect(onSelect).toHaveBeenCalledWith(
216
215
  expect.objectContaining({
217
216
  node: null,
218
217
  previous_node: given.node1,
219
- })
218
+ }),
219
+ );
220
+ });
221
+ });
222
+ });
223
+
224
+ describe("tree.loading_data", () => {
225
+ const server = setupServer(
226
+ http.get("/tree/", () => HttpResponse.json(exampleData)),
227
+ );
228
+ beforeEach(() => {
229
+ server.listen();
230
+ });
231
+
232
+ afterAll(() => {
233
+ server.close();
234
+ });
235
+
236
+ it("fires tree.loading_data when the data is loading from an url", async () => {
237
+ const $tree = $("#tree1");
238
+
239
+ const onLoading = jest.fn();
240
+ $tree.on("tree.loading_data", onLoading);
241
+
242
+ $tree.tree({ dataUrl: "/tree/" });
243
+
244
+ await waitFor(() => {
245
+ expect(onLoading).toHaveBeenCalledWith(
246
+ expect.objectContaining({
247
+ isLoading: true,
248
+ node: null,
249
+ }),
220
250
  );
221
251
  });
252
+
253
+ await waitFor(() => {
254
+ expect(onLoading).toHaveBeenCalledWith(
255
+ expect.objectContaining({
256
+ isLoading: false,
257
+ node: null,
258
+ }),
259
+ );
260
+ });
261
+ });
262
+ });
263
+
264
+ describe("onLoading", () => {
265
+ const server = setupServer(
266
+ http.get("/tree/", () => HttpResponse.json(exampleData)),
267
+ );
268
+ beforeEach(() => {
269
+ server.listen();
270
+ });
271
+
272
+ afterAll(() => {
273
+ server.close();
274
+ });
275
+
276
+ it("calls onLoading", async () => {
277
+ const $tree = $("#tree1");
278
+ const onLoading = jest.fn();
279
+
280
+ $tree.tree({ dataUrl: "/tree/", onLoading });
281
+
282
+ await waitFor(() => {
283
+ expect(onLoading).toHaveBeenCalledWith(false, null, $tree);
284
+ });
285
+
286
+ await waitFor(() => {
287
+ expect(onLoading).toHaveBeenCalledWith(false, null, $tree);
288
+ });
222
289
  });
223
290
  });
@@ -1,6 +1,7 @@
1
1
  import getGiven from "givens";
2
2
  import { screen } from "@testing-library/dom";
3
- import { rest } from "msw";
3
+ import { userEvent } from "@testing-library/user-event";
4
+ import { http, HttpResponse } from "msw";
4
5
  import { setupServer } from "msw/node";
5
6
  import "../../tree.jquery";
6
7
  import { togglerLink } from "../support/testUtil";
@@ -47,16 +48,16 @@ context("when a node has load_on_demand in the data", () => {
47
48
 
48
49
  beforeEach(() => {
49
50
  server.use(
50
- rest.get("/tree/", (request, response, ctx) => {
51
- const parentId = request.url.searchParams.get("node");
51
+ http.get("/tree/", ({ request }) => {
52
+ const url = new URL(request.url);
53
+ const parentId = url.searchParams.get("node");
52
54
 
53
55
  if (parentId === "1") {
54
- return response(
55
- ctx.status(200),
56
- ctx.json([{ id: 2, name: "loaded-on-demand" }]),
57
- );
56
+ return HttpResponse.json([
57
+ { id: 2, name: "loaded-on-demand" },
58
+ ]);
58
59
  } else {
59
- return response(ctx.status(400));
60
+ return new HttpResponse(null, { status: 400 });
60
61
  }
61
62
  }),
62
63
  );
@@ -91,7 +92,8 @@ context("when a node has load_on_demand in the data", () => {
91
92
  );
92
93
 
93
94
  it("loads the subtree", async () => {
94
- togglerLink(given.node.element).trigger("click");
95
+ const toggler = togglerLink(given.node.element);
96
+ await userEvent.click(toggler.get(0) as HTMLElement);
95
97
 
96
98
  await screen.findByText("loaded-on-demand");
97
99
 
@@ -106,20 +108,21 @@ context("when a node has load_on_demand in the data", () => {
106
108
  ]);
107
109
  });
108
110
 
109
- context("when the node is selected and has the focus", () => {
111
+ context("when the node is selected", () => {
110
112
  beforeEach(() => {
111
113
  given.$tree.tree("selectNode", given.node);
112
114
  });
113
115
 
114
- it("keeps the node selected and focused", async () => {
116
+ it("keeps the node selected", async () => {
115
117
  expect(given.node.element).toBeSelected();
116
118
  expect(given.node.element).toBeFocused();
117
119
 
118
- togglerLink(given.node.element).trigger("click");
120
+ const toggler = togglerLink(given.node.element);
121
+ await userEvent.click(toggler.get(0) as HTMLElement);
122
+
119
123
  await screen.findByText("loaded-on-demand");
120
124
 
121
125
  expect(given.node.element).toBeSelected();
122
- expect(given.node.element).toBeFocused();
123
126
  });
124
127
  });
125
128
 
@@ -127,7 +130,9 @@ context("when a node has load_on_demand in the data", () => {
127
130
  it("doesn't select the node", async () => {
128
131
  expect(given.node.element).not.toBeSelected();
129
132
 
130
- togglerLink(given.node.element).trigger("click");
133
+ const toggler = togglerLink(given.node.element);
134
+ await userEvent.click(toggler.get(0) as HTMLElement);
135
+
131
136
  await screen.findByText("loaded-on-demand");
132
137
 
133
138
  expect(given.node.element).not.toBeSelected();
@@ -144,7 +149,9 @@ context("when a node has load_on_demand in the data", () => {
144
149
  expect(given.node.element).toBeSelected();
145
150
  expect(given.node.element).not.toBeFocused();
146
151
 
147
- togglerLink(given.node.element).trigger("click");
152
+ const toggler = togglerLink(given.node.element);
153
+ await userEvent.click(toggler.get(0) as HTMLElement);
154
+
148
155
  await screen.findByText("loaded-on-demand");
149
156
 
150
157
  expect(given.node.element).toBeSelected();
@@ -1,6 +1,7 @@
1
1
  import getGiven from "givens";
2
2
  import { screen, waitFor } from "@testing-library/dom";
3
- import { rest } from "msw";
3
+ import { http, HttpResponse } from "msw";
4
+ import { userEvent } from "@testing-library/user-event";
4
5
  import { setupServer } from "msw/node";
5
6
  import "../../tree.jquery";
6
7
  import exampleData from "../support/exampleData";
@@ -769,9 +770,7 @@ describe("loadDataFromUrl", () => {
769
770
 
770
771
  beforeEach(() => {
771
772
  server.use(
772
- rest.get("/tree/", (_request, response, ctx) =>
773
- response(ctx.status(200), ctx.json(given.serverData)),
774
- ),
773
+ http.get("/tree/", () => HttpResponse.json(given.serverData)),
775
774
  );
776
775
 
777
776
  given.$tree.tree({ data: given.initialData });
@@ -1031,11 +1030,7 @@ describe("reload", () => {
1031
1030
  given("$tree", () => $("#tree1"));
1032
1031
 
1033
1032
  beforeEach(async () => {
1034
- server.use(
1035
- rest.get("/tree2/", (_request, response, ctx) =>
1036
- response(ctx.status(200), ctx.json(exampleData)),
1037
- ),
1038
- );
1033
+ server.use(http.get("/tree2/", () => HttpResponse.json(exampleData)));
1039
1034
 
1040
1035
  given.$tree.tree({ dataUrl: "/tree2/" });
1041
1036
  await screen.findByText("node1");
@@ -1255,9 +1250,11 @@ describe("setOption", () => {
1255
1250
  given("node1", () => given.$tree.tree("getNodeByNameMustExist", "node1"));
1256
1251
  given("$tree", () => $("#tree1"));
1257
1252
 
1258
- it("sets an option", () => {
1253
+ it("sets an option", async () => {
1259
1254
  given.$tree.tree("setOption", "selectable", true);
1260
- titleSpan(given.node1.element).trigger("click");
1255
+ await userEvent.click(
1256
+ titleSpan(given.node1.element).get(0) as HTMLElement,
1257
+ );
1261
1258
  expect(given.$tree.tree("getSelectedNode")).toMatchObject({
1262
1259
  name: "node1",
1263
1260
  });
@@ -0,0 +1,82 @@
1
+ import "../../tree.jquery";
2
+ import { userEvent } from "@testing-library/user-event";
3
+ import exampleData from "../support/exampleData";
4
+ import { titleSpan, togglerLink } from "../support/testUtil";
5
+
6
+ beforeEach(() => {
7
+ $("body").append('<div id="tree1"></div>');
8
+ });
9
+
10
+ afterEach(() => {
11
+ const $tree = $("#tree1");
12
+ $tree.tree("destroy");
13
+ $tree.remove();
14
+ });
15
+
16
+ it("selects a node and sets the focus when it is clicked", async () => {
17
+ const $tree = $("#tree1");
18
+ $tree.tree({ data: exampleData });
19
+
20
+ const node = $tree.tree("getNodeByNameMustExist", "node1");
21
+ expect(node.element).not.toBeSelected();
22
+ expect(node.element).not.toBeFocused();
23
+
24
+ await userEvent.click(titleSpan(node.element).get(0) as HTMLElement);
25
+
26
+ expect(node.element).toBeSelected();
27
+ });
28
+
29
+ it("deselects when a selected node is clicked", async () => {
30
+ const $tree = $("#tree1");
31
+ $tree.tree({ data: exampleData });
32
+
33
+ const node = $tree.tree("getNodeByNameMustExist", "node1");
34
+ $tree.tree("selectNode", node);
35
+
36
+ expect(node.element).toBeSelected();
37
+
38
+ await userEvent.click(titleSpan(node.element).get(0) as HTMLElement);
39
+
40
+ expect(node.element).not.toBeSelected();
41
+ });
42
+
43
+ it("opens a node when the toggle button is clicked", async () => {
44
+ const $tree = $("#tree1");
45
+ $tree.tree({ data: exampleData });
46
+
47
+ const node = $tree.tree("getNodeByNameMustExist", "node1");
48
+ expect(node.element).not.toBeOpen();
49
+
50
+ await userEvent.click(togglerLink(node.element).get(0) as HTMLElement);
51
+
52
+ expect(node.element).toBeOpen();
53
+ });
54
+
55
+ it("doesn't select a node when it is opened", async () => {
56
+ const $tree = $("#tree1");
57
+ $tree.tree({ data: exampleData });
58
+
59
+ const node = $tree.tree("getNodeByNameMustExist", "node1");
60
+ expect(node.element).not.toBeSelected();
61
+ expect(node.element).not.toBeOpen();
62
+
63
+ await userEvent.click(togglerLink(node.element).get(0) as HTMLElement);
64
+
65
+ expect(node.element).not.toBeSelected();
66
+ expect(node.element).toBeOpen();
67
+ });
68
+
69
+ it("keeps it selected when a selected node is opened", async () => {
70
+ const $tree = $("#tree1");
71
+ $tree.tree({ data: exampleData });
72
+
73
+ const node = $tree.tree("getNodeByNameMustExist", "node1");
74
+ $tree.tree("selectNode", node);
75
+ expect(node.element).toBeSelected();
76
+ expect(node.element).not.toBeOpen();
77
+
78
+ await userEvent.click(togglerLink(node.element).get(0) as HTMLElement);
79
+
80
+ expect(node.element).toBeSelected();
81
+ expect(node.element).toBeOpen();
82
+ });
@@ -1,6 +1,6 @@
1
1
  import getGiven from "givens";
2
2
  import { screen, waitFor } from "@testing-library/dom";
3
- import { rest } from "msw";
3
+ import { http, HttpResponse } from "msw";
4
4
  import { setupServer } from "msw/node";
5
5
  import "../../tree.jquery";
6
6
  import exampleData from "../support/exampleData";
@@ -257,11 +257,11 @@ describe("dataUrl", () => {
257
257
 
258
258
  beforeEach(() => {
259
259
  server.use(
260
- rest.get("/tree/", (request, response, ctx) => {
260
+ http.get("/tree/", ({ request }) => {
261
261
  const nodeName = request.headers.get("node");
262
262
  const data = nodeName ? [nodeName] : exampleData;
263
263
 
264
- return response(ctx.status(200), ctx.json(data));
264
+ return HttpResponse.json(data);
265
265
  }),
266
266
  );
267
267
  });
@@ -411,11 +411,12 @@ describe("onLoadFailed", () => {
411
411
  context("when the loading fails", () => {
412
412
  beforeEach(() => {
413
413
  server.use(
414
- rest.get("/tree/", (_request, response, ctx) =>
415
- response(
416
- ctx.status(500),
417
- ctx.body("Internal server error"),
418
- ),
414
+ http.get(
415
+ "/tree/",
416
+ () =>
417
+ new HttpResponse("Internal server error", {
418
+ status: 500,
419
+ }),
419
420
  ),
420
421
  );
421
422
  });
@@ -1,5 +1,6 @@
1
1
  import getGiven from "givens";
2
- import { Node, Position } from "../node";
2
+ import { Node } from "../node";
3
+ import { Position } from "../position";
3
4
  import exampleData from "./support/exampleData";
4
5
  import "jest-extended";
5
6
 
@@ -1,4 +1,4 @@
1
- import { getPosition, getPositionName, Position } from "../node";
1
+ import { getPosition, getPositionName, Position } from "../position";
2
2
 
3
3
  const context = describe;
4
4