@hyperframes/studio 0.7.57 → 0.7.58
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/dist/assets/{hyperframes-player--Z69cEkE.js → hyperframes-player-CtTDO63S.js} +1 -1
- package/dist/assets/{index-DfmYkU44.js → index-B_UvTX3E.js} +204 -204
- package/dist/assets/{index-KsfE1bUu.js → index-C47jAC3Q.js} +1 -1
- package/dist/assets/{index-Bf1x1y8H.js → index-DeQPzqwH.js} +1 -1
- package/dist/assets/index-uahwWkgw.css +1 -0
- package/dist/{chunk-5QSIMBEJ.js → chunk-OBAG3GWK.js} +33 -21
- package/dist/chunk-OBAG3GWK.js.map +1 -0
- package/dist/{domEditingLayers-6LQGKPOI.js → domEditingLayers-2CKWGEHS.js} +2 -2
- package/dist/index.d.ts +15 -4
- package/dist/index.html +2 -2
- package/dist/index.js +5954 -4474
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +3 -3
- package/src/components/StudioFeedbackBar.tsx +2 -2
- package/src/components/editor/CanvasContextMenu.test.tsx +27 -14
- package/src/components/editor/CanvasContextMenu.tsx +71 -6
- package/src/components/editor/DomEditOverlay.tsx +5 -2
- package/src/components/editor/LayersPanel.tsx +110 -99
- package/src/components/editor/canvasContextMenuZOrder.test.ts +57 -0
- package/src/components/editor/canvasContextMenuZOrder.ts +40 -1
- package/src/components/editor/domEditingDom.ts +5 -6
- package/src/components/editor/domEditingElement.ts +15 -32
- package/src/components/editor/layersPanelSort.ts +80 -0
- package/src/components/editor/useLayerRevealOverride.test.ts +169 -0
- package/src/components/editor/useLayerRevealOverride.ts +389 -0
- package/src/components/nle/PreviewOverlays.tsx +27 -2
- package/src/components/nle/TimelinePane.test.ts +3 -0
- package/src/components/nle/TimelinePane.tsx +11 -1
- package/src/components/nle/useCanvasZOrderTimelineMirror.test.tsx +440 -0
- package/src/components/nle/useCanvasZOrderTimelineMirror.ts +191 -0
- package/src/components/nle/useTimelineEditCallbacks.ts +1 -0
- package/src/components/nle/zLaneGesture.test.ts +176 -0
- package/src/components/nle/zLaneGesture.ts +77 -0
- package/src/hooks/domEditCommitTypes.ts +17 -1
- package/src/hooks/elementLifecycleOpsTestUtils.ts +25 -0
- package/src/hooks/fetchStubTestUtils.ts +16 -0
- package/src/hooks/timelineEditingHelpers.test.ts +92 -1
- package/src/hooks/timelineEditingHelpers.ts +32 -15
- package/src/hooks/timelineMoveAdapter.ts +2 -1
- package/src/hooks/timelineTimingSync.test.ts +703 -33
- package/src/hooks/timelineTimingSync.ts +282 -97
- package/src/hooks/timelineTrackVisibility.test.ts +3 -1
- package/src/hooks/timelineTrackVisibility.ts +4 -3
- package/src/hooks/useClipboard.ts +19 -17
- package/src/hooks/useDomEditCommits.test.tsx +316 -38
- package/src/hooks/useDomEditCommits.ts +143 -78
- package/src/hooks/useDomEditSession.test.tsx +3 -1
- package/src/hooks/useDomEditSession.ts +1 -1
- package/src/hooks/useElementLifecycleOps.test.tsx +426 -60
- package/src/hooks/useElementLifecycleOps.ts +174 -81
- package/src/hooks/usePreviewPersistence.ts +2 -2
- package/src/hooks/useTimelineEditing.test.tsx +302 -154
- package/src/hooks/useTimelineEditing.ts +97 -93
- package/src/hooks/useTimelineEditingTypes.ts +1 -1
- package/src/hooks/useTimelineGroupEditing.ts +35 -3
- package/src/player/components/Timeline.test.ts +31 -21
- package/src/player/components/Timeline.tsx +62 -91
- package/src/player/components/TimelineCanvas.tsx +36 -5
- package/src/player/components/TimelineLanes.tsx +32 -10
- package/src/player/components/TimelineOverlays.tsx +36 -0
- package/src/player/components/TimelineRuler.tsx +18 -5
- package/src/player/components/TrackGapContextMenu.tsx +116 -0
- package/src/player/components/timelineCallbacks.ts +4 -1
- package/src/player/components/timelineClipDragCommit.test.ts +279 -3
- package/src/player/components/timelineClipDragCommit.ts +173 -71
- package/src/player/components/timelineGapCommit.test.ts +163 -0
- package/src/player/components/timelineGapCommit.ts +103 -0
- package/src/player/components/timelineGaps.test.ts +221 -0
- package/src/player/components/timelineGaps.ts +182 -0
- package/src/player/components/timelineLayout.test.ts +2 -1
- package/src/player/components/timelineLayout.ts +22 -6
- package/src/player/components/timelineMarquee.test.ts +20 -10
- package/src/player/components/timelineMarquee.ts +9 -2
- package/src/player/components/timelineStackingSync.test.ts +42 -1
- package/src/player/components/timelineStackingSync.ts +31 -16
- package/src/player/components/timelineZMirror.test.ts +667 -0
- package/src/player/components/timelineZMirror.ts +393 -0
- package/src/player/components/timelineZoom.test.ts +2 -2
- package/src/player/components/timelineZoom.ts +7 -3
- package/src/player/components/useTimelineClipDrag.ts +1 -1
- package/src/player/components/useTimelineGapHighlights.test.ts +119 -0
- package/src/player/components/useTimelineGapHighlights.ts +110 -0
- package/src/player/components/useTimelinePlayhead.ts +4 -3
- package/src/player/components/useTimelineRangeSelection.ts +6 -4
- package/src/player/components/useTimelineScrollViewport.ts +73 -0
- package/src/player/components/useTimelineStackingSync.test.tsx +0 -1
- package/src/player/components/useTimelineStackingSync.ts +5 -3
- package/src/player/components/useTimelineTrackDerivations.ts +38 -0
- package/src/player/components/useTrackGapMenu.ts +155 -0
- package/src/player/hooks/useExpandedTimelineElements.test.ts +67 -4
- package/src/player/hooks/useExpandedTimelineElements.ts +12 -1
- package/src/player/hooks/useTimelineSyncCallbacks.ts +2 -0
- package/src/player/lib/layerOrdering.test.ts +1 -15
- package/src/player/lib/layerOrdering.ts +5 -28
- package/src/player/lib/timelineDOM.test.ts +72 -0
- package/src/player/lib/timelineDOM.ts +3 -0
- package/src/player/lib/timelineElementHelpers.ts +35 -11
- package/src/player/store/playerStore.ts +12 -1
- package/src/telemetry/events.test.ts +10 -0
- package/src/telemetry/events.ts +1 -0
- package/src/utils/domEditSaveQueue.ts +4 -3
- package/src/utils/editHistory.test.ts +82 -0
- package/src/utils/editHistory.ts +14 -2
- package/src/utils/gsapSoftReload.test.ts +54 -115
- package/src/utils/gsapSoftReload.ts +42 -152
- package/src/utils/gsapUndoRestore.test.ts +269 -0
- package/src/utils/gsapUndoRestore.ts +269 -0
- package/src/utils/sourceScopedSelectorIndex.ts +29 -0
- package/dist/assets/index-_pqzyxB1.css +0 -1
- package/dist/chunk-5QSIMBEJ.js.map +0 -1
- /package/dist/{domEditingLayers-6LQGKPOI.js.map → domEditingLayers-2CKWGEHS.js.map} +0 -0
|
@@ -276,18 +276,20 @@ describe("useDomEditCommits z-index reorder persistence", () => {
|
|
|
276
276
|
_init?: Parameters<typeof fetch>[1],
|
|
277
277
|
): Promise<Response> => {
|
|
278
278
|
const url = requestUrl(input);
|
|
279
|
-
if (url.
|
|
280
|
-
if (url.includes("/file-mutations/patch-elements-batch/")) {
|
|
279
|
+
if (url.endsWith("/file-mutations/patch-element-batches")) {
|
|
281
280
|
return jsonResponse({
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
281
|
+
durable: true,
|
|
282
|
+
files: [
|
|
283
|
+
{
|
|
284
|
+
sourceFile: "index.html",
|
|
285
|
+
changed: true,
|
|
286
|
+
matched: [true, true, true],
|
|
287
|
+
before: original,
|
|
288
|
+
after,
|
|
289
|
+
},
|
|
290
|
+
],
|
|
286
291
|
});
|
|
287
292
|
}
|
|
288
|
-
if (url.includes("/file-mutations/patch-element/")) {
|
|
289
|
-
return jsonResponse({ ok: true, changed: true, matched: true, content: after });
|
|
290
|
-
}
|
|
291
293
|
throw new Error(`Unexpected fetch: ${url}`);
|
|
292
294
|
},
|
|
293
295
|
);
|
|
@@ -317,25 +319,34 @@ describe("useDomEditCommits z-index reorder persistence", () => {
|
|
|
317
319
|
});
|
|
318
320
|
|
|
319
321
|
const batchPosts = fetchMock.mock.calls.filter(([input]) =>
|
|
320
|
-
requestUrl(input).
|
|
322
|
+
requestUrl(input).endsWith("/file-mutations/patch-element-batches"),
|
|
321
323
|
);
|
|
322
324
|
const singlePosts = fetchMock.mock.calls.filter(([input]) =>
|
|
323
|
-
requestUrl(input).includes("/file-mutations/patch-
|
|
325
|
+
requestUrl(input).includes("/file-mutations/patch-elements-batch/"),
|
|
324
326
|
);
|
|
325
327
|
expect(batchPosts).toHaveLength(1);
|
|
326
328
|
expect(singlePosts).toHaveLength(0);
|
|
327
329
|
expect(JSON.parse(String(batchPosts[0]?.[1]?.body))).toEqual({
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
330
|
+
batches: [
|
|
331
|
+
{
|
|
332
|
+
sourceFile: "index.html",
|
|
333
|
+
patches: expect.arrayContaining([
|
|
334
|
+
expect.objectContaining({ target: expect.objectContaining({ id: "a" }) }),
|
|
335
|
+
expect.objectContaining({ target: expect.objectContaining({ id: "b" }) }),
|
|
336
|
+
expect.objectContaining({ target: expect.objectContaining({ id: "c" }) }),
|
|
337
|
+
]),
|
|
338
|
+
},
|
|
339
|
+
],
|
|
333
340
|
});
|
|
334
341
|
expect(rendered.recordEdit).toHaveBeenCalledTimes(1);
|
|
335
342
|
expect(rendered.recordEdit).toHaveBeenCalledWith({
|
|
336
343
|
label: "Reorder layers",
|
|
337
344
|
kind: "manual",
|
|
338
345
|
coalesceKey: "z-reorder:test",
|
|
346
|
+
// Unbounded per-gesture fold window (keys are unique per gesture):
|
|
347
|
+
// the z entry and its mirror/lane counterpart fold across the server
|
|
348
|
+
// round-trip that separates them.
|
|
349
|
+
coalesceMs: Number.POSITIVE_INFINITY,
|
|
339
350
|
files: { "index.html": { before: original, after } },
|
|
340
351
|
});
|
|
341
352
|
// FIX: a z-only reorder must NOT remount the preview iframe ("the blink").
|
|
@@ -354,9 +365,11 @@ describe("useDomEditCommits z-index reorder persistence", () => {
|
|
|
354
365
|
const after = '<div id="a" style="z-index: 2"></div>';
|
|
355
366
|
const fetchMock = vi.fn(async (input: Parameters<typeof fetch>[0]): Promise<Response> => {
|
|
356
367
|
const url = requestUrl(input);
|
|
357
|
-
if (url.
|
|
358
|
-
|
|
359
|
-
|
|
368
|
+
if (url.endsWith("/file-mutations/patch-element-batches")) {
|
|
369
|
+
return jsonResponse({
|
|
370
|
+
durable: true,
|
|
371
|
+
files: [{ sourceFile: "index.html", changed: true, before: original, after }],
|
|
372
|
+
});
|
|
360
373
|
}
|
|
361
374
|
throw new Error(`Unexpected fetch: ${url}`);
|
|
362
375
|
});
|
|
@@ -380,18 +393,26 @@ describe("useDomEditCommits z-index reorder persistence", () => {
|
|
|
380
393
|
});
|
|
381
394
|
|
|
382
395
|
it("warns and reports telemetry for unmatched batch patches without throwing", async () => {
|
|
383
|
-
// The server reports per-patch matched[]: #b was not found in the source
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
// path must NOT engage — the reload reconverges the preview with disk.
|
|
396
|
+
// The server reports per-patch matched[]: #b was not found in the source,
|
|
397
|
+
// so it atomically refuses the whole multi-file gesture. The reload
|
|
398
|
+
// reconverges the preview with disk while the lifecycle owner rolls back
|
|
399
|
+
// both optimistic writes.
|
|
388
400
|
const original = '<div id="a" style="z-index: 1"></div>';
|
|
389
|
-
const after = '<div id="a" style="z-index: 2"></div>';
|
|
390
401
|
const fetchMock = vi.fn(async (input: Parameters<typeof fetch>[0]): Promise<Response> => {
|
|
391
402
|
const url = requestUrl(input);
|
|
392
|
-
if (url.
|
|
393
|
-
|
|
394
|
-
|
|
403
|
+
if (url.endsWith("/file-mutations/patch-element-batches")) {
|
|
404
|
+
return jsonResponse({
|
|
405
|
+
durable: false,
|
|
406
|
+
files: [
|
|
407
|
+
{
|
|
408
|
+
sourceFile: "index.html",
|
|
409
|
+
changed: false,
|
|
410
|
+
matched: [true, false],
|
|
411
|
+
before: original,
|
|
412
|
+
after: original,
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
});
|
|
395
416
|
}
|
|
396
417
|
throw new Error(`Unexpected fetch: ${url}`);
|
|
397
418
|
});
|
|
@@ -412,10 +433,10 @@ describe("useDomEditCommits z-index reorder persistence", () => {
|
|
|
412
433
|
]);
|
|
413
434
|
});
|
|
414
435
|
|
|
415
|
-
// No throw:
|
|
416
|
-
expect(element.style.zIndex).toBe("
|
|
417
|
-
expect(second.style.zIndex).toBe("
|
|
418
|
-
expect(rendered.recordEdit).
|
|
436
|
+
// No throw: incomplete durability rolls back both optimistic writes.
|
|
437
|
+
expect(element.style.zIndex).toBe("");
|
|
438
|
+
expect(second.style.zIndex).toBe("");
|
|
439
|
+
expect(rendered.recordEdit).not.toHaveBeenCalled();
|
|
419
440
|
expect(rendered.reloadPreview).toHaveBeenCalledTimes(1);
|
|
420
441
|
expect(warnSpy).toHaveBeenCalledWith(
|
|
421
442
|
expect.stringContaining("could not match 1 patch target(s) in index.html"),
|
|
@@ -435,12 +456,125 @@ describe("useDomEditCommits z-index reorder persistence", () => {
|
|
|
435
456
|
}
|
|
436
457
|
});
|
|
437
458
|
|
|
438
|
-
it("
|
|
439
|
-
const
|
|
459
|
+
it("uses one atomic request and rolls back every file when one target is unmatched", async () => {
|
|
460
|
+
const indexOriginal = '<div id="a" style="z-index: 4"></div>';
|
|
461
|
+
const sceneOriginal = '<div id="b" style="z-index: 5"></div>';
|
|
462
|
+
const fetchMock = vi.fn(
|
|
463
|
+
async (
|
|
464
|
+
input: Parameters<typeof fetch>[0],
|
|
465
|
+
init?: Parameters<typeof fetch>[1],
|
|
466
|
+
): Promise<Response> => {
|
|
467
|
+
const url = requestUrl(input);
|
|
468
|
+
if (url.endsWith("/file-mutations/patch-element-batches")) {
|
|
469
|
+
expect(JSON.parse(String(init?.body))).toEqual({
|
|
470
|
+
batches: [
|
|
471
|
+
expect.objectContaining({ sourceFile: "index.html" }),
|
|
472
|
+
expect.objectContaining({ sourceFile: "scene.html" }),
|
|
473
|
+
],
|
|
474
|
+
});
|
|
475
|
+
return jsonResponse({
|
|
476
|
+
durable: false,
|
|
477
|
+
files: [
|
|
478
|
+
{
|
|
479
|
+
sourceFile: "index.html",
|
|
480
|
+
changed: false,
|
|
481
|
+
matched: [true],
|
|
482
|
+
before: indexOriginal,
|
|
483
|
+
after: indexOriginal,
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
sourceFile: "scene.html",
|
|
487
|
+
changed: false,
|
|
488
|
+
matched: [false],
|
|
489
|
+
before: sceneOriginal,
|
|
490
|
+
after: sceneOriginal,
|
|
491
|
+
},
|
|
492
|
+
],
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
496
|
+
},
|
|
497
|
+
);
|
|
498
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
499
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
500
|
+
const { iframe, element } = createPreviewElement(
|
|
501
|
+
'<div data-hf-id="hf-card" style="z-index: 4"></div><div id="b" style="z-index: 5"></div>',
|
|
502
|
+
);
|
|
503
|
+
element.id = "a";
|
|
504
|
+
const second = iframe.contentDocument!.getElementById("b")!;
|
|
505
|
+
const rendered = renderDomEditCommits(createSelection(element), iframe);
|
|
506
|
+
|
|
507
|
+
try {
|
|
508
|
+
let result:
|
|
509
|
+
| Awaited<ReturnType<typeof rendered.hook.handleDomZIndexReorderCommit>>
|
|
510
|
+
| undefined;
|
|
511
|
+
await act(async () => {
|
|
512
|
+
result = await rendered.hook.handleDomZIndexReorderCommit([
|
|
513
|
+
{ element, zIndex: 8, id: "a", sourceFile: "index.html" },
|
|
514
|
+
{ element: second, zIndex: 9, id: "b", sourceFile: "scene.html" },
|
|
515
|
+
]);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
expect(result).toEqual({ durable: false, allMatched: false, changed: false });
|
|
519
|
+
expect(element.style.zIndex).toBe("4");
|
|
520
|
+
expect(second.style.zIndex).toBe("5");
|
|
521
|
+
expect(rendered.recordEdit).not.toHaveBeenCalled();
|
|
522
|
+
expect(rendered.reloadPreview).toHaveBeenCalledTimes(1);
|
|
523
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
524
|
+
} finally {
|
|
525
|
+
warnSpy.mockRestore();
|
|
526
|
+
rendered.cleanup();
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("reloads and reports non-durable when an unmatched batch changes no bytes", async () => {
|
|
531
|
+
const original = '<div id="a" style="z-index: 1"></div>';
|
|
532
|
+
const fetchMock = vi.fn(async (input: Parameters<typeof fetch>[0]): Promise<Response> => {
|
|
533
|
+
const url = requestUrl(input);
|
|
534
|
+
if (url.endsWith("/file-mutations/patch-element-batches")) {
|
|
535
|
+
return jsonResponse({
|
|
536
|
+
durable: false,
|
|
537
|
+
files: [
|
|
538
|
+
{
|
|
539
|
+
sourceFile: "index.html",
|
|
540
|
+
changed: false,
|
|
541
|
+
matched: [false],
|
|
542
|
+
before: original,
|
|
543
|
+
after: original,
|
|
544
|
+
},
|
|
545
|
+
],
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
549
|
+
});
|
|
550
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
551
|
+
const { iframe, element } = createPreviewElement();
|
|
552
|
+
element.id = "a";
|
|
553
|
+
const rendered = renderDomEditCommits(createSelection(element), iframe);
|
|
554
|
+
|
|
555
|
+
try {
|
|
556
|
+
let result:
|
|
557
|
+
| Awaited<ReturnType<typeof rendered.hook.handleDomZIndexReorderCommit>>
|
|
558
|
+
| undefined;
|
|
559
|
+
await act(async () => {
|
|
560
|
+
result = await rendered.hook.handleDomZIndexReorderCommit([
|
|
561
|
+
{ element, zIndex: 2, id: "missing", sourceFile: "index.html" },
|
|
562
|
+
]);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
expect(result).toEqual({ durable: false, allMatched: false, changed: false });
|
|
566
|
+
expect(element.style.zIndex).toBe("");
|
|
567
|
+
expect(rendered.recordEdit).not.toHaveBeenCalled();
|
|
568
|
+
expect(rendered.reloadPreview).toHaveBeenCalledTimes(1);
|
|
569
|
+
} finally {
|
|
570
|
+
rendered.cleanup();
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it("rolls back and reloads after a rejected batch POST", async () => {
|
|
440
575
|
const fetchMock = vi.fn(async (input: Parameters<typeof fetch>[0]): Promise<Response> => {
|
|
441
576
|
const url = requestUrl(input);
|
|
442
|
-
if (url.
|
|
443
|
-
if (url.includes("/file-mutations/patch-elements-batch/")) {
|
|
577
|
+
if (url.endsWith("/file-mutations/patch-element-batches")) {
|
|
444
578
|
return jsonResponse({ error: "batch rejected" }, 500);
|
|
445
579
|
}
|
|
446
580
|
throw new Error(`Unexpected fetch: ${url}`);
|
|
@@ -485,10 +619,154 @@ describe("useDomEditCommits z-index reorder persistence", () => {
|
|
|
485
619
|
]);
|
|
486
620
|
expect(writeProjectFile).not.toHaveBeenCalled();
|
|
487
621
|
expect(rendered.recordEdit).not.toHaveBeenCalled();
|
|
488
|
-
expect(rendered.reloadPreview).
|
|
622
|
+
expect(rendered.reloadPreview).toHaveBeenCalledTimes(1);
|
|
623
|
+
expect(
|
|
624
|
+
fetchMock.mock.calls.filter(([input]) =>
|
|
625
|
+
requestUrl(input).endsWith("/file-mutations/patch-element-batches"),
|
|
626
|
+
),
|
|
627
|
+
).toHaveLength(1);
|
|
628
|
+
} finally {
|
|
629
|
+
rendered.cleanup();
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it("reloads after the server commits but the aggregate response is lost", async () => {
|
|
634
|
+
const original = '<div data-hf-id="hf-card" id="a" style="z-index: 7"></div>';
|
|
635
|
+
const after = '<div data-hf-id="hf-card" id="a" style="z-index: 2"></div>';
|
|
636
|
+
let diskContent = original;
|
|
637
|
+
const fetchMock = vi.fn(async (input: Parameters<typeof fetch>[0]): Promise<Response> => {
|
|
638
|
+
const url = requestUrl(input);
|
|
639
|
+
if (url.endsWith("/file-mutations/patch-element-batches")) {
|
|
640
|
+
diskContent = after;
|
|
641
|
+
throw new TypeError("connection reset after commit");
|
|
642
|
+
}
|
|
643
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
644
|
+
});
|
|
645
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
646
|
+
const { iframe, element } = createPreviewElement(original);
|
|
647
|
+
usePlayerStore.getState().setElements([
|
|
648
|
+
{
|
|
649
|
+
id: "a",
|
|
650
|
+
tag: "div",
|
|
651
|
+
start: 0,
|
|
652
|
+
duration: 1,
|
|
653
|
+
track: 0,
|
|
654
|
+
zIndex: 7,
|
|
655
|
+
hasExplicitZIndex: true,
|
|
656
|
+
},
|
|
657
|
+
]);
|
|
658
|
+
const rendered = renderDomEditCommits(createSelection(element), iframe);
|
|
659
|
+
|
|
660
|
+
try {
|
|
661
|
+
let rejection: unknown;
|
|
662
|
+
await act(async () => {
|
|
663
|
+
try {
|
|
664
|
+
await rendered.hook.handleDomZIndexReorderCommit([
|
|
665
|
+
{ element, zIndex: 2, id: "a", sourceFile: "index.html", key: "a" },
|
|
666
|
+
]);
|
|
667
|
+
} catch (error) {
|
|
668
|
+
rejection = error;
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
expect(rejection).toBeInstanceOf(Error);
|
|
673
|
+
expect((rejection as Error).message).toContain("connection reset after commit");
|
|
674
|
+
expect(diskContent).toBe(after);
|
|
675
|
+
expect(element.style.zIndex).toBe("7");
|
|
676
|
+
expect(usePlayerStore.getState().elements[0]).toMatchObject({
|
|
677
|
+
zIndex: 7,
|
|
678
|
+
hasExplicitZIndex: true,
|
|
679
|
+
});
|
|
680
|
+
expect(rendered.recordEdit).not.toHaveBeenCalled();
|
|
681
|
+
expect(rendered.reloadPreview).toHaveBeenCalledTimes(1);
|
|
682
|
+
expect(
|
|
683
|
+
fetchMock.mock.calls.filter(([input]) =>
|
|
684
|
+
requestUrl(input).endsWith("/file-mutations/patch-element-batches"),
|
|
685
|
+
),
|
|
686
|
+
).toHaveLength(1);
|
|
687
|
+
} finally {
|
|
688
|
+
rendered.cleanup();
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
it.each([
|
|
693
|
+
[
|
|
694
|
+
"changed=false with different snapshots",
|
|
695
|
+
{
|
|
696
|
+
durable: true,
|
|
697
|
+
files: [
|
|
698
|
+
{
|
|
699
|
+
sourceFile: "index.html",
|
|
700
|
+
changed: false,
|
|
701
|
+
matched: [true],
|
|
702
|
+
before: '<div id="a" style="z-index: 1"></div>',
|
|
703
|
+
after: '<div id="a" style="z-index: 2"></div>',
|
|
704
|
+
},
|
|
705
|
+
],
|
|
706
|
+
},
|
|
707
|
+
],
|
|
708
|
+
[
|
|
709
|
+
"changed=true with identical snapshots",
|
|
710
|
+
{
|
|
711
|
+
durable: true,
|
|
712
|
+
files: [
|
|
713
|
+
{
|
|
714
|
+
sourceFile: "index.html",
|
|
715
|
+
changed: true,
|
|
716
|
+
matched: [true],
|
|
717
|
+
before: '<div id="a" style="z-index: 1"></div>',
|
|
718
|
+
after: '<div id="a" style="z-index: 1"></div>',
|
|
719
|
+
},
|
|
720
|
+
],
|
|
721
|
+
},
|
|
722
|
+
],
|
|
723
|
+
[
|
|
724
|
+
"durable=false with a changed file",
|
|
725
|
+
{
|
|
726
|
+
durable: false,
|
|
727
|
+
files: [
|
|
728
|
+
{
|
|
729
|
+
sourceFile: "index.html",
|
|
730
|
+
changed: true,
|
|
731
|
+
matched: [true],
|
|
732
|
+
before: '<div id="a" style="z-index: 1"></div>',
|
|
733
|
+
after: '<div id="a" style="z-index: 2"></div>',
|
|
734
|
+
},
|
|
735
|
+
],
|
|
736
|
+
},
|
|
737
|
+
],
|
|
738
|
+
])("rejects and reloads for malformed 200 response: %s", async (_label, responseBody) => {
|
|
739
|
+
const fetchMock = vi.fn(async (input: Parameters<typeof fetch>[0]): Promise<Response> => {
|
|
740
|
+
const url = requestUrl(input);
|
|
741
|
+
if (url.endsWith("/file-mutations/patch-element-batches")) {
|
|
742
|
+
return jsonResponse(responseBody);
|
|
743
|
+
}
|
|
744
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
745
|
+
});
|
|
746
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
747
|
+
const { iframe, element } = createPreviewElement();
|
|
748
|
+
element.id = "a";
|
|
749
|
+
const rendered = renderDomEditCommits(createSelection(element), iframe);
|
|
750
|
+
|
|
751
|
+
try {
|
|
752
|
+
let rejection: unknown;
|
|
753
|
+
await act(async () => {
|
|
754
|
+
try {
|
|
755
|
+
await rendered.hook.handleDomZIndexReorderCommit([
|
|
756
|
+
{ element, zIndex: 2, id: "a", sourceFile: "index.html", key: "a" },
|
|
757
|
+
]);
|
|
758
|
+
} catch (error) {
|
|
759
|
+
rejection = error;
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
expect(rejection).toBeInstanceOf(Error);
|
|
764
|
+
expect((rejection as Error).message).toBe("Invalid atomic element patch response");
|
|
765
|
+
expect(rendered.recordEdit).not.toHaveBeenCalled();
|
|
766
|
+
expect(rendered.reloadPreview).toHaveBeenCalledTimes(1);
|
|
489
767
|
expect(
|
|
490
768
|
fetchMock.mock.calls.filter(([input]) =>
|
|
491
|
-
requestUrl(input).
|
|
769
|
+
requestUrl(input).endsWith("/file-mutations/patch-element-batches"),
|
|
492
770
|
),
|
|
493
771
|
).toHaveLength(1);
|
|
494
772
|
} finally {
|