Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions apps/openstory/stories/renderer.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,11 @@ const Scene = (props: SceneProps) => {
mouseX={mouseX()}
isPromptMode={props.isPromptMode}
inputValue={props.inputValue}
isPendingDismiss={props.isPendingDismiss}
discardPrompt={
props.isPendingDismiss
? { kind: "standard", onConfirm: noop, onCancel: noop }
: undefined
}
isFrozen={false}
toolbarVisible={props.showToolbar}
isActive={props.isActive}
Expand All @@ -336,7 +340,6 @@ const Scene = (props: SceneProps) => {
onInputSubmit={noop}
onToggleExpand={noop}
onConfirmDismiss={noop}
onCancelDismiss={noop}
onToggleActive={noop}
onToolbarStateChange={noop}
onContextMenuDismiss={noop}
Expand Down
3 changes: 1 addition & 2 deletions apps/openstory/stories/selection-label.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const baseProps: SelectionLabelProps = {
onSubmit: noop,
onDismiss: noop,
onConfirmDismiss: noop,
onCancelDismiss: noop,
onAcknowledgeError: noop,
onRetry: noop,
onShowContextMenu: noop,
Expand Down Expand Up @@ -122,7 +121,7 @@ export const PendingDismiss: Story = {
tagName: "header",
componentName: "Header",
isPromptMode: true,
isPendingDismiss: true,
discardPrompt: { kind: "standard", onConfirm: noop, onCancel: noop },
inputValue: "tweak the spacing",
},
};
Expand Down
18 changes: 18 additions & 0 deletions packages/react-grab/e2e/edit-panel-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,24 @@ export const clickHeaderCopyButton = async (page: Page): Promise<void> => {
);
};

export const clickDiscardButton = async (
page: Page,
action: "cancel" | "confirm" | "copy",
): Promise<void> => {
await page.evaluate(
({ attrName, actionName }) => {
const host = document.querySelector(`[${attrName}]`);
const shadowRoot = host?.shadowRoot;
const button = shadowRoot?.querySelector<HTMLButtonElement>(
`[data-react-grab-discard-button='${actionName}']`,
);
if (!button) throw new Error("Discard button not found");
button.click();
},
{ attrName: ATTRIBUTE_NAME, actionName: action },
);
};

export const dragActiveSlider = async (page: Page): Promise<void> => {
const sliderBounds = await page.evaluate(
({ attrName, propertyAttr }) => {
Expand Down
115 changes: 109 additions & 6 deletions packages/react-grab/e2e/edit-panel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
IDLE_BUFFER_MS,
SEARCH_INPUT_ATTR,
clearEditStorage,
clickDiscardButton,
clickHeaderCopyButton,
dispatchOutsideDismiss,
dragActiveSlider,
Expand All @@ -25,7 +26,6 @@ import {
getOverlayFocusVisualStates,
getPropertyRowBounds,
getSearchInputFocusVisualState,
getVisibleSliderVisualState,
getVisiblePropertyKeys,
hoverVisibleSlider,
isDiscardPromptVisible,
Expand Down Expand Up @@ -285,6 +285,110 @@ test.describe("Style Panel", () => {
expect(await isDiscardPromptVisible(reactGrab.page)).toBe(true);
});

test("mouse movement after keyboard tweak opens discard prompt with Copy", async ({
reactGrab,
}) => {
await openEditPanel(reactGrab, BUTTON_SELECTOR);
await reactGrab.page.keyboard.press("ArrowRight");
await reactGrab.page.waitForTimeout(80);
const inlineStyleAfterTweak = await getInlineStyleAttribute(reactGrab.page, BUTTON_SELECTOR);
expect(inlineStyleAfterTweak.length).toBeGreaterThan(0);

await reactGrab.page.mouse.move(10, 10);
await reactGrab.page.waitForTimeout(80);
expect(await isEditPanelVisible(reactGrab.page)).toBe(true);
expect(await isEditPanelCompact(reactGrab.page)).toBe(false);
expect(await isDiscardPromptVisible(reactGrab.page)).toBe(true);

await clickDiscardButton(reactGrab.page, "copy");
await expect.poll(() => isEditPanelVisible(reactGrab.page)).toBe(false);
expect(await getInlineStyleAttribute(reactGrab.page, BUTTON_SELECTOR)).toBe(
inlineStyleAfterTweak,
);
});

test("canceling mouse-move discard prompt consumes the pointer handoff", async ({
reactGrab,
}) => {
await openEditPanel(reactGrab, BUTTON_SELECTOR);
await reactGrab.page.keyboard.press("ArrowRight");
await reactGrab.page.waitForTimeout(80);

await reactGrab.page.mouse.move(10, 10);
await reactGrab.page.waitForTimeout(80);
expect(await isDiscardPromptVisible(reactGrab.page)).toBe(true);

await clickDiscardButton(reactGrab.page, "cancel");
await reactGrab.page.waitForTimeout(80);
expect(await isDiscardPromptVisible(reactGrab.page)).toBe(false);

await reactGrab.page.mouse.move(20, 20);
await reactGrab.page.waitForTimeout(80);
expect(await isEditPanelVisible(reactGrab.page)).toBe(true);
expect(await isDiscardPromptVisible(reactGrab.page)).toBe(false);
});

test("canceling outside-click discard prompt consumes the pointer handoff", async ({
reactGrab,
}) => {
await openEditPanel(reactGrab, BUTTON_SELECTOR);
await reactGrab.page.keyboard.press("ArrowRight");
await reactGrab.page.waitForTimeout(80);

await dispatchOutsideDismiss(reactGrab.page);
await reactGrab.page.waitForTimeout(80);
expect(await isDiscardPromptVisible(reactGrab.page)).toBe(true);

await clickDiscardButton(reactGrab.page, "cancel");
await reactGrab.page.waitForTimeout(80);
expect(await isDiscardPromptVisible(reactGrab.page)).toBe(false);

await reactGrab.page.mouse.move(20, 20);
await reactGrab.page.waitForTimeout(80);
expect(await isEditPanelVisible(reactGrab.page)).toBe(true);
expect(await isDiscardPromptVisible(reactGrab.page)).toBe(false);
});

test("mouse movement while discard prompt is visible does not consume the handoff", async ({
reactGrab,
}) => {
await openEditPanel(reactGrab, BUTTON_SELECTOR);
await reactGrab.page.keyboard.press("ArrowRight");
await reactGrab.page.waitForTimeout(80);

await openDiscardPromptViaEscape(reactGrab.page);
expect(await isDiscardPromptVisible(reactGrab.page)).toBe(true);
await reactGrab.page.mouse.move(10, 10);
await reactGrab.page.waitForTimeout(80);
expect(await isDiscardPromptVisible(reactGrab.page)).toBe(true);

await clickDiscardButton(reactGrab.page, "cancel");
await reactGrab.page.waitForTimeout(80);
expect(await isDiscardPromptVisible(reactGrab.page)).toBe(false);

await reactGrab.page.mouse.move(20, 20);
await reactGrab.page.waitForTimeout(80);
expect(await isEditPanelVisible(reactGrab.page)).toBe(true);
expect(await isDiscardPromptVisible(reactGrab.page)).toBe(false);
});

test("keyboard navigation after pointer tweak does not arm mouse-move discard prompt", async ({
reactGrab,
}) => {
await openEditPanel(reactGrab, BUTTON_SELECTOR);
await dragActiveSlider(reactGrab.page);
await reactGrab.page.waitForTimeout(80);
expect(await isHeaderCopyButtonVisible(reactGrab.page)).toBe(true);

await reactGrab.page.keyboard.press("ArrowDown");
await reactGrab.page.waitForTimeout(80);
await reactGrab.page.mouse.move(10, 10);
await reactGrab.page.waitForTimeout(80);

expect(await isEditPanelVisible(reactGrab.page)).toBe(true);
expect(await isDiscardPromptVisible(reactGrab.page)).toBe(false);
});

test("net-zero tweak dismiss restores preview inline styles", async ({ reactGrab }) => {
await openEditPanel(reactGrab, BUTTON_SELECTOR);
const beforeTweak = await getInlineStyleAttribute(reactGrab.page, BUTTON_SELECTOR);
Expand Down Expand Up @@ -777,17 +881,16 @@ test.describe("Style Panel", () => {
expect(await isEditPanelCompact(reactGrab.page)).toBe(true);
});

test("compact slider shows unit marks on hover", async ({ reactGrab }) => {
test("compact keyboard tweak opens discard prompt on hover", async ({ reactGrab }) => {
await openEditPanel(reactGrab, BUTTON_SELECTOR);
await reactGrab.page.keyboard.press("ArrowRight");
await reactGrab.page.waitForTimeout(IDLE_BUFFER_MS);
expect(await isEditPanelCompact(reactGrab.page)).toBe(true);
const beforeHover = await getVisibleSliderVisualState(reactGrab.page);
expect(beforeHover.maxHashMarkOpacity).toBe(0);

await hoverVisibleSlider(reactGrab.page);
await reactGrab.page.waitForTimeout(IDLE_BUFFER_MS);
const afterHover = await getVisibleSliderVisualState(reactGrab.page);
expect(afterHover.maxHashMarkOpacity).toBeGreaterThan(0);
expect(await isEditPanelCompact(reactGrab.page)).toBe(false);
expect(await isDiscardPromptVisible(reactGrab.page)).toBe(true);
});

test("typing in search re-expands the compact panel", async ({ reactGrab }) => {
Expand Down
Loading
Loading