mirror of
https://github.com/kamranahmedse/driver.js.git
synced 2026-11-01 05:07:12 +08:00
packages/driver is the library (still published as driver.js, with repository.directory set); playground and docs move to apps/. One root pnpm workspace and lockfile replace the three standalone projects, with turbo orchestrating build/test/lint:package (apps depend on the library build; local + CI caching via .turbo). Apps keep importing the library from source for HMR — the aliases now point at ../../packages/driver/src and each app gains a workspace:* dependency so the task graph knows the edge. The dev-server fs scope widens to the repo root accordingly. Releases run from packages/driver (root `pnpm release` delegates). The changelog still lives in the docs app, so the release scripts reach it via ../../../apps/docs and the after:bump hook stages it explicitly — release-it no longer sits above both directories. The merged workspace settings keep sharp unbuilt on purpose (prebuilt binaries; building from source needs node-gyp and fails) — the docs app had this right and the playground setting was the accident. postcss and postcss-scss were unreferenced devDependencies and are dropped; prettier moves to the root as the only shared tool.
78 lines
2.5 KiB
TypeScript
78 lines
2.5 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import { createDriver, nextFrame, popoverTitle, SAMPLE_STEPS, useDriverHarness } from "./utils";
|
|
|
|
useDriverHarness();
|
|
|
|
// Advances the rAF loop until the predicate holds or the frame budget runs out.
|
|
// The animated stage transition settles over several frames, so polling beats a
|
|
// fixed wait.
|
|
async function flushFrames(predicate: () => boolean, maxFrames = 120): Promise<void> {
|
|
for (let i = 0; i < maxFrames; i++) {
|
|
if (predicate()) {
|
|
return;
|
|
}
|
|
await nextFrame();
|
|
}
|
|
throw new Error("condition not met within frame budget");
|
|
}
|
|
|
|
// The library drives the CSS fade via a custom property on <body> so a single
|
|
// `duration` config controls both the JS stage slide and the CSS fade-in.
|
|
const cssDuration = () => document.body.style.getPropertyValue("--driver-animation-duration");
|
|
|
|
describe("animation duration", () => {
|
|
it("defaults to 400ms", () => {
|
|
const d = createDriver({ steps: SAMPLE_STEPS });
|
|
|
|
expect(d.getConfig().duration).toBe(400);
|
|
});
|
|
|
|
it("respects a custom duration via config", () => {
|
|
const d = createDriver({ duration: 1200, steps: SAMPLE_STEPS });
|
|
|
|
expect(d.getConfig().duration).toBe(1200);
|
|
});
|
|
|
|
it("exposes the duration to CSS as a custom property while driving", () => {
|
|
const d = createDriver({ duration: 1200, steps: SAMPLE_STEPS });
|
|
d.drive();
|
|
|
|
expect(cssDuration()).toBe("1200ms");
|
|
});
|
|
|
|
it("falls back to the default on the custom property when duration is not set", () => {
|
|
const d = createDriver({ steps: SAMPLE_STEPS });
|
|
d.drive();
|
|
|
|
expect(cssDuration()).toBe("400ms");
|
|
});
|
|
|
|
it("clears the custom property when the tour is destroyed", () => {
|
|
const d = createDriver({ duration: 1200, steps: SAMPLE_STEPS });
|
|
d.drive();
|
|
d.destroy();
|
|
|
|
expect(cssDuration()).toBe("");
|
|
});
|
|
});
|
|
|
|
describe("animated stage transition", () => {
|
|
it("runs the rAF stage transition and settles on the next step", async () => {
|
|
const onHighlighted = vi.fn();
|
|
const d = createDriver({ animate: true, duration: 30, steps: SAMPLE_STEPS, onHighlighted });
|
|
d.drive();
|
|
|
|
await flushFrames(() => onHighlighted.mock.calls.length >= 1);
|
|
expect(d.getActiveIndex()).toBe(0);
|
|
|
|
d.moveNext();
|
|
|
|
// Drives transitionStage/easeInOutQuad across frames until step 2 settles.
|
|
await flushFrames(() => onHighlighted.mock.calls.length >= 2);
|
|
|
|
expect(d.getActiveIndex()).toBe(1);
|
|
expect(popoverTitle()).toBe("Step 2");
|
|
expect(d.getState("__activeStep")?.popover?.title).toBe("Step 2");
|
|
});
|
|
});
|