@window-splitter/state 0.2.4 → 0.3.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.
@@ -6,14 +6,20 @@ import { expect, test, describe, vi } from "vitest";
6
6
  import {
7
7
  buildTemplate,
8
8
  dragHandlePayload,
9
+ getPanelGroupPercentageSizes,
10
+ getPanelGroupPixelSizes,
11
+ getPanelPercentageSize,
12
+ getPanelPixelSize,
9
13
  groupMachine,
10
14
  GroupMachineEvent,
11
15
  initializePanel,
12
16
  initializePanelHandleData,
13
17
  isPanelHandle,
18
+ prepareSnapshot,
14
19
  } from "./index.js";
15
20
  import { Actor, createActor } from "xstate";
16
21
  import { spring } from "framer-motion";
22
+ import Big from "big.js";
17
23
 
18
24
  function getTemplate(actor: Actor<typeof groupMachine>) {
19
25
  return buildTemplate(actor.getSnapshot().context);
@@ -33,7 +39,7 @@ function dragHandle(
33
39
  type: "dragHandle",
34
40
  handleId: options.id,
35
41
  value: dragHandlePayload({
36
- orientation: options.orientation || "horizontal",
42
+ orientation: options.orientation,
37
43
  delta: options.delta > 0 ? 1 : -1,
38
44
  shiftKey: options.shiftKey,
39
45
  }),
@@ -186,6 +192,86 @@ describe("constraints", () => {
186
192
  );
187
193
  });
188
194
 
195
+ test("works with 2 simple panels - overshot flakiness", () => {
196
+ const actor = createActor(groupMachine, {
197
+ input: { groupId: "group" },
198
+ }).start();
199
+
200
+ sendAll(actor, [
201
+ {
202
+ type: "registerPanel",
203
+ data: initializePanel({ id: "panel-1" }),
204
+ },
205
+ {
206
+ type: "registerPanelHandle",
207
+ // @ts-expect-error The types were hard and i wanted to keep the public API simple
208
+ data: { id: "resizer-1", size: { type: "pixel", value: new Big(10) } },
209
+ },
210
+ { type: "registerPanel", data: initializePanel({ id: "panel-2" }) },
211
+ ]);
212
+
213
+ expect(getTemplate(actor)).toMatchInlineSnapshot(
214
+ `"minmax(0px, 1fr) 10px minmax(0px, 1fr)"`
215
+ );
216
+ initializeSizes(actor, { width: 500, height: 200 });
217
+
218
+ // The only way i could simulate the leftoverSpace was to set the size
219
+ // during a drag
220
+ capturePixelValues(actor, () => {
221
+ actor.send({
222
+ type: "setSize",
223
+ size: {
224
+ width: 600,
225
+ height: 200,
226
+ },
227
+ });
228
+ actor.send({
229
+ type: "dragHandle",
230
+ handleId: "resizer-1",
231
+ value: dragHandlePayload({
232
+ delta: 30,
233
+ }),
234
+ });
235
+ });
236
+
237
+ expect(getTemplate(actor)).toMatchInlineSnapshot(
238
+ `"minmax(0px, min(calc(0.46610169491525423729 * (100% - 10px)), 100%)) 10px minmax(0px, min(calc(0.53389830508474576271 * (100% - 10px)), 100%))"`
239
+ );
240
+ });
241
+
242
+ test("works with 2 simple panels - max percents", () => {
243
+ const actor = createActor(groupMachine, {
244
+ input: { groupId: "group" },
245
+ }).start();
246
+
247
+ sendAll(actor, [
248
+ {
249
+ type: "registerPanel",
250
+ data: initializePanel({ id: "panel-1", max: "40%" }),
251
+ },
252
+ {
253
+ type: "registerPanelHandle",
254
+ // @ts-expect-error The types were hard and i wanted to keep the public API simple
255
+ data: { id: "resizer-1", size: { type: "pixel", value: new Big(10) } },
256
+ },
257
+ { type: "registerPanel", data: initializePanel({ id: "panel-2" }) },
258
+ ]);
259
+
260
+ expect(getTemplate(actor)).toMatchInlineSnapshot(
261
+ `"minmax(0px, 40%) 10px minmax(0px, 1fr)"`
262
+ );
263
+ initializeSizes(actor, { width: 500, height: 200 });
264
+
265
+ // Drag the resizer to the right
266
+ capturePixelValues(actor, () => {
267
+ dragHandle(actor, { id: "resizer-1", delta: 10 });
268
+ });
269
+
270
+ expect(getTemplate(actor)).toMatchInlineSnapshot(
271
+ `"minmax(0px, min(calc(0.40816326530612244898 * (100% - 10px)), 40%)) 10px minmax(0px, min(calc(0.59183673469387755102 * (100% - 10px)), 100%))"`
272
+ );
273
+ });
274
+
189
275
  test("works with 2 simple panels - vertical", () => {
190
276
  const actor = createActor(groupMachine, {
191
277
  input: { orientation: "vertical", groupId: "group" },
@@ -274,6 +360,29 @@ describe("constraints", () => {
274
360
  });
275
361
  });
276
362
 
363
+ test("can spy on resize", () => {
364
+ const spy = vi.fn();
365
+ const actor = createActor(groupMachine, {
366
+ input: {
367
+ groupId: "group",
368
+ initialItems: [
369
+ initializePanel({ id: "panel-1", onResize: { current: spy } }),
370
+ initializePanelHandleData({ id: "resizer-1", size: "10px" }),
371
+ initializePanel({ id: "panel-2" }),
372
+ ],
373
+ },
374
+ }).start();
375
+ initializeSizes(actor, { width: 500, height: 200 });
376
+
377
+ capturePixelValues(actor, () => {
378
+ dragHandle(actor, { id: "resizer-1", delta: 100 });
379
+ expect(spy).toHaveBeenCalledWith({
380
+ pixel: 345,
381
+ percentage: 0.69,
382
+ });
383
+ });
384
+ });
385
+
277
386
  test("no delta does nothing", () => {
278
387
  const actor = createActor(groupMachine, {
279
388
  input: {
@@ -292,7 +401,7 @@ describe("constraints", () => {
292
401
  actor.send({
293
402
  type: "dragHandle",
294
403
  handleId: "resizer-1",
295
- value: dragHandlePayload({ delta: 0, orientation: "horizontal" }),
404
+ value: dragHandlePayload({ delta: 0 }),
296
405
  });
297
406
  expect(getTemplate(actor)).toMatchInlineSnapshot(`"245px 10px 245px"`);
298
407
  });
@@ -569,6 +678,8 @@ describe("collapsible panel", () => {
569
678
  test.each([
570
679
  undefined,
571
680
  "ease-in-out" as const,
681
+ "bounce" as const,
682
+ { duration: 500, easing: "linear" as const },
572
683
  { duration: 1000, easing: springEasing },
573
684
  ])("panel can be collapsible: %s", async (animation) => {
574
685
  const actor = createActor(groupMachine, {
@@ -992,7 +1103,7 @@ describe("collapsible panel", () => {
992
1103
  });
993
1104
  });
994
1105
 
995
- test("panel collapse can be controlled", async () => {
1106
+ test("panel collapse can be controlled ", async () => {
996
1107
  const actor = createActor(groupMachine, {
997
1108
  input: { groupId: "group" },
998
1109
  }).start();
@@ -1069,6 +1180,8 @@ describe("collapsible panel", () => {
1069
1180
 
1070
1181
  spy.mockReset();
1071
1182
 
1183
+ // Sending controlled events works
1184
+
1072
1185
  actor.send({ type: "collapsePanel", panelId: "panel-2", controlled: true });
1073
1186
  await waitForIdle(actor);
1074
1187
 
@@ -1085,6 +1198,81 @@ describe("collapsible panel", () => {
1085
1198
  expect(getTemplate(actor)).toMatchInlineSnapshot(`"321px 10px 169px"`);
1086
1199
  });
1087
1200
  });
1201
+
1202
+ test("panel collapse can be controlled - event", async () => {
1203
+ const actor = createActor(groupMachine, {
1204
+ input: { groupId: "group" },
1205
+ }).start();
1206
+
1207
+ const spy = vi.fn();
1208
+
1209
+ sendAll(actor, [
1210
+ { type: "registerPanel", data: initializePanel({ id: "panel-1" }) },
1211
+ {
1212
+ type: "registerPanelHandle",
1213
+ data: { id: "resizer-1", size: "10px" },
1214
+ },
1215
+ {
1216
+ type: "registerPanel",
1217
+ data: initializePanel({
1218
+ id: "panel-2",
1219
+ collapsible: true,
1220
+ min: "100px",
1221
+ // This marks the collapse as controlled
1222
+ collapsed: false,
1223
+ collapsedSize: "20px",
1224
+ onCollapseChange: {
1225
+ current: spy,
1226
+ },
1227
+ }),
1228
+ },
1229
+ ]);
1230
+
1231
+ expect(getTemplate(actor)).toMatchInlineSnapshot(
1232
+ `"minmax(0px, 1fr) 10px minmax(100px, 1fr)"`
1233
+ );
1234
+ initializeSizes(actor, { width: 500, height: 200 });
1235
+
1236
+ // COLLAPSE
1237
+
1238
+ // An uncontrolled event doesn't collapse the panel, it only calls the onCollapseChange
1239
+ actor.send({ type: "collapsePanel", panelId: "panel-2" });
1240
+ expect(spy).toHaveBeenCalledWith(true);
1241
+ await waitForIdle(actor);
1242
+ capturePixelValues(actor, () => {
1243
+ expect(getTemplate(actor)).toMatchInlineSnapshot(`"245px 10px 245px"`);
1244
+ });
1245
+ spy.mockReset();
1246
+
1247
+ // Sending controlled events works
1248
+ actor.send({ type: "collapsePanel", panelId: "panel-2", controlled: true });
1249
+ // and doesn't call the onCollapseChange
1250
+ expect(spy).not.toHaveBeenCalled();
1251
+ await waitForIdle(actor);
1252
+ capturePixelValues(actor, () => {
1253
+ expect(getTemplate(actor)).toMatchInlineSnapshot(`"470px 10px 20px"`);
1254
+ });
1255
+
1256
+ // EXPAND
1257
+
1258
+ // An uncontrolled event doesn't expand the panel, it only calls the onCollapseChange
1259
+ actor.send({ type: "expandPanel", panelId: "panel-2" });
1260
+ expect(spy).toHaveBeenCalledWith(false);
1261
+ await waitForIdle(actor);
1262
+ capturePixelValues(actor, () => {
1263
+ expect(getTemplate(actor)).toMatchInlineSnapshot(`"470px 10px 20px"`);
1264
+ });
1265
+ spy.mockReset();
1266
+
1267
+ // Sending controlled events works
1268
+ actor.send({ type: "expandPanel", panelId: "panel-2", controlled: true });
1269
+ // and doesn't call the onCollapseChange
1270
+ expect(spy).not.toHaveBeenCalled();
1271
+ await waitForIdle(actor);
1272
+ capturePixelValues(actor, () => {
1273
+ expect(getTemplate(actor)).toMatchInlineSnapshot(`"245px 10px 245px"`);
1274
+ });
1275
+ });
1088
1276
  });
1089
1277
 
1090
1278
  describe("conditional panel", () => {
@@ -1307,13 +1495,13 @@ describe("conditional panel", () => {
1307
1495
  input: {
1308
1496
  groupId: "group",
1309
1497
  initialItems: [
1310
- initializePanel({ id: "panel-1" }),
1498
+ initializePanel({ id: "panel-1", max: "20px" }),
1311
1499
  initializePanelHandleData({ id: "resizer-1", size: "10px" }),
1312
- initializePanel({ id: "panel-2", max: "50px" }),
1500
+ initializePanel({ id: "panel-2", default: "10px", max: "50px" }),
1313
1501
  initializePanelHandleData({ id: "resizer-2", size: "10px" }),
1314
1502
  initializePanel({ id: "panel-3", default: "300px" }),
1315
1503
  initializePanelHandleData({ id: "resizer-3", size: "10px" }),
1316
- initializePanel({ id: "panel-4", max: "50px" }),
1504
+ initializePanel({ id: "panel-4", default: "10px", max: "50px" }),
1317
1505
  initializePanelHandleData({ id: "resizer-4", size: "10px" }),
1318
1506
  initializePanel({ id: "panel-5", max: "300px" }),
1319
1507
  ],
@@ -1324,7 +1512,7 @@ describe("conditional panel", () => {
1324
1512
 
1325
1513
  capturePixelValues(actor, () => {
1326
1514
  expect(getTemplate(actor)).toMatchInlineSnapshot(
1327
- `"0px 10px 50px 10px 300px 10px 50px 10px 60px"`
1515
+ `"20px 10px 10px 10px 300px 10px 10px 10px 120px"`
1328
1516
  );
1329
1517
  });
1330
1518
 
@@ -1335,7 +1523,7 @@ describe("conditional panel", () => {
1335
1523
 
1336
1524
  capturePixelValues(actor, () => {
1337
1525
  expect(getTemplate(actor)).toMatchInlineSnapshot(
1338
- `"70px 10px 50px 10px 50px 10px 300px"`
1526
+ `"20px 10px 50px 10px 50px 10px 300px"`
1339
1527
  );
1340
1528
  });
1341
1529
  });
@@ -1394,7 +1582,7 @@ describe("errors", () => {
1394
1582
  {
1395
1583
  type: "dragHandle",
1396
1584
  handleId: "handle-2",
1397
- value: dragHandlePayload({ delta: 100, orientation: "horizontal" }),
1585
+ value: dragHandlePayload({ delta: 100 }),
1398
1586
  },
1399
1587
  ]);
1400
1588
 
@@ -1403,3 +1591,133 @@ describe("errors", () => {
1403
1591
  );
1404
1592
  });
1405
1593
  });
1594
+
1595
+ describe("utils", async () => {
1596
+ test("getPanelGroupPixelSizes", () => {
1597
+ const actor = createActor(groupMachine, {
1598
+ input: { groupId: "group" },
1599
+ }).start();
1600
+
1601
+ sendAll(actor, [
1602
+ { type: "registerPanel", data: initializePanel({ id: "panel-1" }) },
1603
+ {
1604
+ type: "registerPanelHandle",
1605
+ data: { id: "resizer-1", size: "10px" },
1606
+ },
1607
+ {
1608
+ type: "registerPanel",
1609
+ data: initializePanel({ id: "panel-2", max: "300px" }),
1610
+ },
1611
+ ]);
1612
+
1613
+ initializeSizes(actor, { width: 500, height: 200 });
1614
+
1615
+ expect(getPanelGroupPixelSizes(actor.getSnapshot().context)).toEqual([
1616
+ 190, 10, 300,
1617
+ ]);
1618
+ });
1619
+
1620
+ test("getPanelGroupPercentageSizes", () => {
1621
+ const actor = createActor(groupMachine, {
1622
+ input: { groupId: "group" },
1623
+ }).start();
1624
+
1625
+ sendAll(actor, [
1626
+ { type: "registerPanel", data: initializePanel({ id: "panel-1" }) },
1627
+ {
1628
+ type: "registerPanelHandle",
1629
+ data: { id: "resizer-1", size: "10px" },
1630
+ },
1631
+ {
1632
+ type: "registerPanel",
1633
+ data: initializePanel({ id: "panel-2", max: "300px" }),
1634
+ },
1635
+ ]);
1636
+
1637
+ initializeSizes(actor, { width: 500, height: 200 });
1638
+
1639
+ expect(getPanelGroupPercentageSizes(actor.getSnapshot().context)).toEqual([
1640
+ 0.3877551020408163, 0.02, 0.6122448979591837,
1641
+ ]);
1642
+ });
1643
+
1644
+ test("getPanelPercentageSize", () => {
1645
+ const actor = createActor(groupMachine, {
1646
+ input: { groupId: "group" },
1647
+ }).start();
1648
+
1649
+ sendAll(actor, [
1650
+ { type: "registerPanel", data: initializePanel({ id: "panel-1" }) },
1651
+ {
1652
+ type: "registerPanelHandle",
1653
+ data: { id: "resizer-1", size: "10px" },
1654
+ },
1655
+ {
1656
+ type: "registerPanel",
1657
+ data: initializePanel({ id: "panel-2", max: "300px" }),
1658
+ },
1659
+ ]);
1660
+
1661
+ initializeSizes(actor, { width: 500, height: 200 });
1662
+
1663
+ expect(getPanelPercentageSize(actor.getSnapshot().context, "panel-2")).toBe(
1664
+ 0.6
1665
+ );
1666
+ });
1667
+
1668
+ test("getPanelPixelSize", () => {
1669
+ const actor = createActor(groupMachine, {
1670
+ input: { groupId: "group" },
1671
+ }).start();
1672
+
1673
+ sendAll(actor, [
1674
+ { type: "registerPanel", data: initializePanel({ id: "panel-1" }) },
1675
+ {
1676
+ type: "registerPanelHandle",
1677
+ data: { id: "resizer-1", size: "10px" },
1678
+ },
1679
+ {
1680
+ type: "registerPanel",
1681
+ data: initializePanel({ id: "panel-2", max: "300px" }),
1682
+ },
1683
+ ]);
1684
+
1685
+ initializeSizes(actor, { width: 500, height: 200 });
1686
+
1687
+ expect(getPanelPixelSize(actor.getSnapshot().context, "panel-2")).toBe(300);
1688
+ });
1689
+
1690
+ test("prepareSnapshot", () => {
1691
+ const actor = createActor(groupMachine, {
1692
+ input: { groupId: "group" },
1693
+ }).start();
1694
+
1695
+ sendAll(actor, [
1696
+ {
1697
+ type: "registerPanel",
1698
+ data: initializePanel({ id: "panel-1", min: "100px" }),
1699
+ },
1700
+ {
1701
+ type: "registerPanelHandle",
1702
+ data: { id: "resizer-1", size: "10px" },
1703
+ },
1704
+ {
1705
+ type: "registerPanel",
1706
+ data: initializePanel({
1707
+ id: "panel-2",
1708
+ max: "300px",
1709
+ collapsible: true,
1710
+ collapsedSize: "50px",
1711
+ }),
1712
+ },
1713
+ ]);
1714
+
1715
+ initializeSizes(actor, { width: 500, height: 200 });
1716
+
1717
+ const oldSnapshot = actor.getSnapshot();
1718
+ const serialized = JSON.stringify(oldSnapshot);
1719
+ const newSnapshot = prepareSnapshot(JSON.parse(serialized));
1720
+
1721
+ expect(newSnapshot).toMatchSnapshot();
1722
+ });
1723
+ });
package/src/utils.test.ts CHANGED
@@ -5,6 +5,8 @@ import {
5
5
  getCollapsiblePanelForHandleId,
6
6
  groupMachine,
7
7
  initializePanel,
8
+ getCursor,
9
+ initializePanelHandleData,
8
10
  } from "./index.js";
9
11
  import { createActor } from "xstate";
10
12
  import Big from "big.js";
@@ -115,3 +117,87 @@ describe("getCollapsiblePanelForHandleId", () => {
115
117
  );
116
118
  });
117
119
  });
120
+
121
+ describe("getCursor", () => {
122
+ describe("horizontal", () => {
123
+ test("works before overshoot", () => {
124
+ expect(
125
+ getCursor({ orientation: "horizontal", dragOvershoot: new Big(-10) })
126
+ ).toBe("e-resize");
127
+ });
128
+
129
+ test("works at overshoot", () => {
130
+ expect(
131
+ getCursor({ orientation: "horizontal", dragOvershoot: new Big(0) })
132
+ ).toBe("ew-resize");
133
+ });
134
+
135
+ test("works after overshoot", () => {
136
+ expect(
137
+ getCursor({ orientation: "horizontal", dragOvershoot: new Big(10) })
138
+ ).toBe("w-resize");
139
+ });
140
+ });
141
+
142
+ describe("vertical", () => {
143
+ test("works before overshoot", () => {
144
+ expect(
145
+ getCursor({ orientation: "vertical", dragOvershoot: new Big(-10) })
146
+ ).toBe("s-resize");
147
+ });
148
+
149
+ test("works at overshoot", () => {
150
+ expect(
151
+ getCursor({ orientation: "vertical", dragOvershoot: new Big(0) })
152
+ ).toBe("ns-resize");
153
+ });
154
+
155
+ test("works after overshoot", () => {
156
+ expect(
157
+ getCursor({ orientation: "vertical", dragOvershoot: new Big(10) })
158
+ ).toBe("n-resize");
159
+ });
160
+ });
161
+ });
162
+
163
+ describe("initializePanelHandleData", () => {
164
+ test("works with pixel size", () => {
165
+ const data = initializePanelHandleData({
166
+ id: "resizer-1",
167
+ size: "10px",
168
+ });
169
+
170
+ expect(data).toMatchInlineSnapshot(`
171
+ {
172
+ "id": "resizer-1",
173
+ "size": {
174
+ "type": "pixel",
175
+ "value": "10",
176
+ },
177
+ "type": "handle",
178
+ }
179
+ `);
180
+ });
181
+
182
+ test("works with pixel size", () => {
183
+ const data = initializePanelHandleData({
184
+ id: "resizer-1",
185
+ // @ts-expect-error The types were hard and i wanted to keep the public API simple
186
+ size: {
187
+ type: "pixel",
188
+ value: new Big(10),
189
+ },
190
+ });
191
+
192
+ expect(data).toMatchInlineSnapshot(`
193
+ {
194
+ "id": "resizer-1",
195
+ "size": {
196
+ "type": "pixel",
197
+ "value": "10",
198
+ },
199
+ "type": "handle",
200
+ }
201
+ `);
202
+ });
203
+ });
package/vitest.config.ts CHANGED
@@ -4,6 +4,8 @@ export default defineConfig({
4
4
  test: {
5
5
  coverage: {
6
6
  provider: "istanbul",
7
+ reporter: ["text", "html", "json-summary", "json"],
8
+ reportOnFailure: true,
7
9
  },
8
10
  browser: {
9
11
  enabled: true,