Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 6 additions & 5 deletions docs/rest/shared/_optimisticTransform.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,24 @@ import {RestEndpoint} from '@data-client/rest';
export const raceDemo = [
{ delay: 800, store: 'open' },
{ delay: 1000, click: 'button.upvote' },
{ delay: 75, click: 'button.upvote' },
{ delay: 75, click: 'button.upvote' },
{ delay: 75, click: 'button.upvote' },
...Array.from({ length: 4 }, () => ({
delay: 120,
click: 'button.upvote',
})),
// past the slowest increment response (the fixture's 5s), so the
// timeline holds every response
{ wait: 5500 },
{ click: { in: 'store', selector: 'button[aria-label="Diff"]' } },
// the first action (the count's load), then on through each change, time
// to read each diff: 5 = the changes the 4 clicks make, as Diff's "Next
// to read each diff: 6 = the changes the 5 clicks make, as Diff's "Next
// change" counts them (more would idle at the end before the close)
{
click: {
in: 'store',
selector: '[role="group"][aria-label^="Scrubber"] button[data-id]',
},
},
...Array.from({ length: 5 }, () => ({
...Array.from({ length: 6 }, () => ({
delay: 1500,
click: { in: 'store', selector: 'button[aria-label="Next change"]' },
})),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ function fakeStage(
const calls: string[] = [];
const stage: Stage = {
find: target => (target === STORE_TOGGLE ? toggle : found),
point: el => void calls.push(el === toggle ? 'point toggle' : 'point'),
point: el => {
calls.push(el === toggle ? 'point toggle' : 'point');
return true;
},
click: () => void calls.push('click'),
append: (_, text) => void calls.push(`append ${text}`),
store: (open, { pressed, view } = {}) =>
Expand Down Expand Up @@ -59,6 +62,19 @@ it('plays steps in order, at their times', async () => {
expect(player.getState()).toBe('done');
});

it("clicks again without a glide when the pointer hasn't moved", async () => {
const player = createPlayer();
const { stage, calls } = fakeStage();
stage.point = () => {
calls.push('point');
return false;
};
const done = player.play([{ delay: 20, click: 'button' }], stage);
await jest.advanceTimersByTimeAsync(20);
await done;
expect(calls).toEqual(['point', 'click', 'end']);
});

it('pausing keeps the time left', async () => {
const player = createPlayer();
const { stage, calls } = fakeStage();
Expand Down Expand Up @@ -211,6 +227,25 @@ describe('stage', () => {
}
});

it('says whether the pointer moved', () => {
const root = playground();
const stage = createStage(root, () => {});
const [one, two] = root.querySelectorAll('button');
one.getBoundingClientRect = () =>
({ left: 0, top: 0, width: 20, height: 20 }) as DOMRect;
two.getBoundingClientRect = () =>
({ left: 40, top: 0, width: 20, height: 20 }) as DOMRect;
expect(stage.point(one)).toBe(true);
expect(stage.point(one)).toBe(false);
expect(stage.point(two)).toBe(true);
// the page scrolled under a target that stays put (a sticky Store):
// the layer moved, so the pointer must glide back onto it
const layer = root.lastElementChild as HTMLElement;
layer.getBoundingClientRect = () =>
({ left: 0, top: -50, width: 0, height: 0 }) as DOMRect;
expect(stage.point(two)).toBe(true);
});

it('the pointer follows its target when the playground resizes', () => {
let resized: () => void = () => {};
(globalThis as any).ResizeObserver = class {
Expand Down
13 changes: 5 additions & 8 deletions website/src/components/Playground/preview/autorun/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import type { StoreView } from '../store/storeView';
/** What a script acts on: the playground's DOM (./stage.ts) */
export interface Stage {
find(target: Target): HTMLElement | null;
/** Brings `el` into view and moves the pointer onto it */
point(el: HTMLElement): void;
/** Brings `el` into view and moves the pointer onto it; false when it
* was there already (a repeated click), so there's no glide to wait out */
point(el: HTMLElement): boolean;
click(el: HTMLElement): void;
append(el: HTMLElement, text: string): void;
/** Opens or closes the Store, in `view` if given; `pressed`: the pointer
Expand Down Expand Up @@ -95,10 +96,7 @@ async function perform(
// onto the Store's toggle first, as a reader would; where the layout
// shows one
const toggle = stage.find(STORE_TOGGLE);
if (toggle) {
stage.point(toggle);
await sleep(MOVE_MS);
}
if (toggle && stage.point(toggle)) await sleep(MOVE_MS);
return stage.store(step.store === 'open', {
pressed: !!toggle,
view: step.view,
Expand All @@ -115,8 +113,7 @@ async function perform(
throw new Error(`nothing matches ${JSON.stringify(target)}`);
};
const target = 'click' in step ? step.click : step.type;
stage.point(await find(target));
await sleep(MOVE_MS);
if (stage.point(await find(target))) await sleep(MOVE_MS);
if ('click' in step) return stage.click(await find(target));
for (const char of step.text) {
stage.append(await find(target), char);
Expand Down
8 changes: 6 additions & 2 deletions website/src/components/Playground/preview/autorun/stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ export function createStage(root: HTMLElement, showStore: ShowStore): Stage {
let at = { x: 0, y: 0 };
let pointedAt: HTMLElement | undefined;

/** Moves the pointer onto `el`, the tip at its center */
/** Moves the pointer onto `el`, the tip at its center; false when it was
* drawn there already (in the layer's coordinates, which scroll with the
* page) */
const place = (el: HTMLElement) => {
const box = el.getBoundingClientRect();
const origin = layer.getBoundingClientRect();
Expand All @@ -34,12 +36,14 @@ export function createStage(root: HTMLElement, showStore: ShowStore): Stage {
pointer.innerHTML = POINTER_SVG;
layer.append(pointer);
}
const previous = pointer.style.transform;
pointer.style.transform = `translate(${at.x - origin.left}px, ${at.y - origin.top}px)`;
// the first point fades in where it is, then glides from there
if (first) {
void pointer.offsetWidth;
pointer.dataset.shown = '';
}
return first || pointer.style.transform !== previous;
};
/** A press: a ring spreads from the tip (none without Web Animations,
* e.g. jsdom) */
Expand Down Expand Up @@ -87,7 +91,7 @@ export function createStage(root: HTMLElement, showStore: ShowStore): Stage {
revealIn(root, el);
revealOnPage(el);
pointedAt = el;
place(el);
return place(el);
},

click(el) {
Expand Down
Loading