@slidev-react/client 0.2.6 → 0.2.7

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.
Files changed (21) hide show
  1. package/package.json +2 -2
  2. package/src/addons/{registry.test.ts → __tests__/registry.test.ts} +5 -5
  3. package/src/features/presentation/{exportArtifacts.test.ts → __tests__/exportArtifacts.test.ts} +2 -2
  4. package/src/features/presentation/{location.test.ts → __tests__/location.test.ts} +2 -2
  5. package/src/features/presentation/__tests__/path.test.ts +146 -0
  6. package/src/features/presentation/{recordingFilename.test.ts → __tests__/recordingFilename.test.ts} +2 -2
  7. package/src/features/presentation/{session.test.ts → __tests__/session.test.ts} +2 -2
  8. package/src/features/presentation/draw/{persistence.test.ts → __tests__/persistence.test.ts} +2 -2
  9. package/src/features/presentation/navigation/{ShortcutsHelpOverlay.test.tsx → __tests__/ShortcutsHelpOverlay.test.tsx} +3 -3
  10. package/src/features/presentation/navigation/{keyboardShortcuts.test.ts → __tests__/keyboardShortcuts.test.ts} +2 -2
  11. package/src/features/presentation/presenter/{FlowTimelinePreview.test.tsx → __tests__/FlowTimelinePreview.test.tsx} +5 -5
  12. package/src/features/presentation/presenter/{persistence.test.ts → __tests__/persistence.test.ts} +2 -2
  13. package/src/features/presentation/presenter/{presentationSyncBridge.test.ts → __tests__/presentationSyncBridge.test.ts} +2 -2
  14. package/src/features/presentation/stage/__tests__/slideSurface.test.ts +93 -0
  15. package/src/features/presentation/sync/model/{presence.test.ts → __tests__/presence.test.ts} +2 -2
  16. package/src/features/presentation/sync/model/{replication.test.ts → __tests__/replication.test.ts} +2 -2
  17. package/src/features/presentation/sync/model/{status.test.ts → __tests__/status.test.ts} +2 -2
  18. package/src/theme/{ThemeProvider.test.ts → __tests__/ThemeProvider.test.ts} +3 -3
  19. package/src/theme/{registry.test.ts → __tests__/registry.test.ts} +4 -4
  20. package/src/theme/layouts/__tests__/resolveLayout.test.ts +31 -0
  21. package/src/ui/primitives/{Annotate.test.tsx → __tests__/Annotate.test.tsx} +3 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slidev-react/client",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Browser-side React app and presentation UI for slidev-react",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,7 +14,7 @@
14
14
  "shiki": "4.0.1",
15
15
  "shiki-magic-move": "1.2.1",
16
16
  "zod": "4.3.6",
17
- "@slidev-react/core": "0.2.6"
17
+ "@slidev-react/core": "0.2.7"
18
18
  },
19
19
  "peerDependencies": {
20
20
  "@antv/g-svg": ">=2.0.0",
@@ -1,8 +1,8 @@
1
- import { describe, expect, it } from "vitest";
2
- import { listRegisteredAddons, resolveAddonDefinitions, resolveSlideAddons } from "./registry";
3
- import { Insight } from "./insight/Insight";
4
- import { InsightAddonProvider } from "./insight/InsightAddonProvider";
5
- import { SpotlightLayout } from "./insight/SpotlightLayout";
1
+ import { describe, expect, it } from "vite-plus/test";
2
+ import { listRegisteredAddons, resolveAddonDefinitions, resolveSlideAddons } from "../registry";
3
+ import { Insight } from "../insight/Insight";
4
+ import { InsightAddonProvider } from "../insight/InsightAddonProvider";
5
+ import { SpotlightLayout } from "../insight/SpotlightLayout";
6
6
 
7
7
  describe("addon registry", () => {
8
8
  it("registers local addons discovered from the addons directory", () => {
@@ -1,10 +1,10 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { describe, expect, it } from "vite-plus/test";
2
2
  import {
3
3
  createSlideImageFileName,
4
4
  createSlideSnapshotFileName,
5
5
  resolveExportSlidesBaseName,
6
6
  trimPdfExtension,
7
- } from "./exportArtifacts";
7
+ } from "../exportArtifacts";
8
8
 
9
9
  describe("presentation export artifacts", () => {
10
10
  it("removes a trailing pdf extension from document titles", () => {
@@ -1,10 +1,10 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { describe, expect, it } from "vite-plus/test";
2
2
  import {
3
3
  buildSlidesPath,
4
4
  normalizePathname,
5
5
  resolveSessionLocationState,
6
6
  resolveSlidesLocationState,
7
- } from "./location";
7
+ } from "../location";
8
8
 
9
9
  describe("presentation location", () => {
10
10
  it("resolves presenter routes for slides navigation", () => {
@@ -0,0 +1,146 @@
1
+ import { describe, expect, it } from "vite-plus/test";
2
+ import {
3
+ buildRolePathFromBase,
4
+ buildRolePathFromPathname,
5
+ buildStandalonePathFromBase,
6
+ buildStandalonePathFromPathname,
7
+ parsePresentationPath,
8
+ parseStandalonePath,
9
+ resolvePresentationBasePath,
10
+ } from "../path";
11
+
12
+ describe("parsePresentationPath", () => {
13
+ it("parses /presenter as a presenter role with no slide number", () => {
14
+ expect(parsePresentationPath("/presenter")).toEqual({
15
+ role: "presenter",
16
+ slideNumber: null,
17
+ basePath: "",
18
+ });
19
+ });
20
+
21
+ it("parses /presenter/3 as presenter role on slide 3", () => {
22
+ expect(parsePresentationPath("/presenter/3")).toEqual({
23
+ role: "presenter",
24
+ slideNumber: 3,
25
+ basePath: "",
26
+ });
27
+ });
28
+
29
+ it("parses a nested basePath like /app/presenter/5", () => {
30
+ expect(parsePresentationPath("/app/presenter/5")).toEqual({
31
+ role: "presenter",
32
+ slideNumber: 5,
33
+ basePath: "/app",
34
+ });
35
+ });
36
+
37
+ it("returns null for a plain path without a role", () => {
38
+ expect(parsePresentationPath("/slides")).toBeNull();
39
+ });
40
+
41
+ it("returns null for an empty path", () => {
42
+ expect(parsePresentationPath("/")).toBeNull();
43
+ });
44
+
45
+ it("returns null for slide-only paths", () => {
46
+ expect(parsePresentationPath("/3")).toBeNull();
47
+ });
48
+ });
49
+
50
+ describe("parseStandalonePath", () => {
51
+ it("parses /3 as slide number 3", () => {
52
+ expect(parseStandalonePath("/3")).toEqual({
53
+ slideNumber: 3,
54
+ basePath: "",
55
+ });
56
+ });
57
+
58
+ it("parses /app/7 with a basePath", () => {
59
+ expect(parseStandalonePath("/app/7")).toEqual({
60
+ slideNumber: 7,
61
+ basePath: "/app",
62
+ });
63
+ });
64
+
65
+ it("returns null for non-numeric paths", () => {
66
+ expect(parseStandalonePath("/about")).toBeNull();
67
+ });
68
+
69
+ it("returns null for presenter paths (handled by parsePresentationPath)", () => {
70
+ expect(parseStandalonePath("/presenter/3")).toBeNull();
71
+ });
72
+
73
+ it("returns null for slide number 0 or negative", () => {
74
+ expect(parseStandalonePath("/0")).toBeNull();
75
+ expect(parseStandalonePath("/-1")).toBeNull();
76
+ });
77
+ });
78
+
79
+ describe("resolvePresentationBasePath", () => {
80
+ it("returns empty string for root", () => {
81
+ expect(resolvePresentationBasePath("/")).toBe("");
82
+ });
83
+
84
+ it("returns empty string for /index.html", () => {
85
+ expect(resolvePresentationBasePath("/index.html")).toBe("");
86
+ });
87
+
88
+ it("strips /index.html suffix", () => {
89
+ expect(resolvePresentationBasePath("/app/index.html")).toBe("/app");
90
+ });
91
+
92
+ it("extracts basePath from presenter paths", () => {
93
+ expect(resolvePresentationBasePath("/app/presenter/3")).toBe("/app");
94
+ });
95
+
96
+ it("extracts basePath from standalone slide paths", () => {
97
+ expect(resolvePresentationBasePath("/app/5")).toBe("/app");
98
+ });
99
+ });
100
+
101
+ describe("buildRolePathFromBase", () => {
102
+ it("builds /presenter/1 from empty base", () => {
103
+ expect(buildRolePathFromBase("", "presenter", 1)).toBe("/presenter/1");
104
+ });
105
+
106
+ it("builds /app/presenter/5 from /app base", () => {
107
+ expect(buildRolePathFromBase("/app", "presenter", 5)).toBe("/app/presenter/5");
108
+ });
109
+
110
+ it("clamps non-positive slide numbers to 1", () => {
111
+ expect(buildRolePathFromBase("", "presenter", 0)).toBe("/presenter/1");
112
+ expect(buildRolePathFromBase("", "presenter", -3)).toBe("/presenter/1");
113
+ });
114
+
115
+ it("floors fractional slide numbers", () => {
116
+ expect(buildRolePathFromBase("", "presenter", 3.7)).toBe("/presenter/3");
117
+ });
118
+ });
119
+
120
+ describe("buildRolePathFromPathname", () => {
121
+ it("derives basePath from a full pathname and builds the role path", () => {
122
+ expect(buildRolePathFromPathname("/app/presenter/2", "presenter", 5)).toBe(
123
+ "/app/presenter/5",
124
+ );
125
+ });
126
+ });
127
+
128
+ describe("buildStandalonePathFromBase", () => {
129
+ it("builds /3 from empty base", () => {
130
+ expect(buildStandalonePathFromBase("", 3)).toBe("/3");
131
+ });
132
+
133
+ it("builds /app/3 from /app base", () => {
134
+ expect(buildStandalonePathFromBase("/app", 3)).toBe("/app/3");
135
+ });
136
+
137
+ it("clamps non-positive slide numbers to 1", () => {
138
+ expect(buildStandalonePathFromBase("", 0)).toBe("/1");
139
+ });
140
+ });
141
+
142
+ describe("buildStandalonePathFromPathname", () => {
143
+ it("derives basePath from pathname and builds standalone path", () => {
144
+ expect(buildStandalonePathFromPathname("/app/5", 3)).toBe("/app/3");
145
+ });
146
+ });
@@ -1,9 +1,9 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { describe, expect, it } from "vite-plus/test";
2
2
  import {
3
3
  createRecordingDownloadName,
4
4
  resolvePresentationFileNameBase,
5
5
  resolveRecordingFileNameBase,
6
- } from "./recordingFilename";
6
+ } from "../recordingFilename";
7
7
 
8
8
  describe("recording filename", () => {
9
9
  it("prefers exportFilename when present", () => {
@@ -1,9 +1,9 @@
1
- import { afterEach, describe, expect, it, vi } from "vitest";
1
+ import { afterEach, describe, expect, it, vi } from "vite-plus/test";
2
2
  import {
3
3
  buildPresentationEntryUrl,
4
4
  resolvePresentationSession,
5
5
  updateSyncModeInUrl,
6
- } from "./session";
6
+ } from "../session";
7
7
 
8
8
  const originalWindow = globalThis.window;
9
9
 
@@ -1,9 +1,9 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { describe, expect, it } from "vite-plus/test";
2
2
  import {
3
3
  DRAW_STORAGE_VERSION,
4
4
  createPersistedDrawState,
5
5
  parsePersistedDrawState,
6
- } from "./persistence";
6
+ } from "../persistence";
7
7
 
8
8
  describe("draw persistence", () => {
9
9
  it("parses persisted draw state", () => {
@@ -1,7 +1,7 @@
1
1
  import { renderToStaticMarkup } from "react-dom/server";
2
- import { describe, expect, it } from "vitest";
3
- import { ShortcutsHelpOverlay } from "./ShortcutsHelpOverlay";
4
- import { buildShortcutHelpSections } from "./keyboardShortcuts";
2
+ import { describe, expect, it } from "vite-plus/test";
3
+ import { ShortcutsHelpOverlay } from "../ShortcutsHelpOverlay";
4
+ import { buildShortcutHelpSections } from "../keyboardShortcuts";
5
5
 
6
6
  describe("ShortcutsHelpOverlay", () => {
7
7
  it("renders the supported shortcut groups and help triggers", () => {
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { describe, expect, it } from "vite-plus/test";
2
2
  import {
3
3
  buildShortcutHelpSections,
4
4
  createShortcutHelpTriggerState,
@@ -6,7 +6,7 @@ import {
6
6
  registerShortcutHelpKeyDown,
7
7
  registerShortcutHelpKeyUp,
8
8
  resolveNavigationShortcutAction,
9
- } from "./keyboardShortcuts";
9
+ } from "../keyboardShortcuts";
10
10
 
11
11
  describe("keyboardShortcuts", () => {
12
12
  it("treats shift+space as retreat and plain space as advance", () => {
@@ -1,10 +1,10 @@
1
1
  import { renderToStaticMarkup } from "react-dom/server";
2
- import { describe, expect, it, vi } from "vitest";
3
- import { AddonProvider } from "../../../addons/AddonProvider";
2
+ import { describe, expect, it, vi } from "vite-plus/test";
3
+ import { AddonProvider } from "../../../../addons/AddonProvider";
4
4
  import { DEFAULT_SLIDES_VIEWPORT } from "@slidev-react/core/slides/viewport";
5
- import { ThemeProvider } from "../../../theme/ThemeProvider";
6
- import { FlowTimelinePreview } from "./FlowTimelinePreview";
7
- import type { CompiledSlide } from "./types";
5
+ import { ThemeProvider } from "../../../../theme/ThemeProvider";
6
+ import { FlowTimelinePreview } from "../FlowTimelinePreview";
7
+ import type { CompiledSlide } from "../types";
8
8
 
9
9
  const demoSlide: CompiledSlide = {
10
10
  id: "timeline-demo",
@@ -1,9 +1,9 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { describe, expect, it } from "vite-plus/test";
2
2
  import {
3
3
  parsePersistedPresenterCursorMode,
4
4
  parsePersistedPresenterSidebarWidth,
5
5
  parsePersistedPresenterStageScale,
6
- } from "./persistence";
6
+ } from "../persistence";
7
7
 
8
8
  describe("presenter persistence", () => {
9
9
  it("parses allowed stage scale values", () => {
@@ -1,5 +1,5 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import { buildPresentationSharedState, mapRemotePresentationPatch } from "./presentationSyncBridge";
1
+ import { describe, expect, it, vi } from "vite-plus/test";
2
+ import { buildPresentationSharedState, mapRemotePresentationPatch } from "../presentationSyncBridge";
3
3
 
4
4
  describe("presentationSyncBridge", () => {
5
5
  it("builds a shared state payload without mutating timestamps", () => {
@@ -0,0 +1,93 @@
1
+ import { describe, expect, it } from "vite-plus/test";
2
+ import { resolveSlideSurface, resolveSlideSurfaceClassName } from "../slideSurface";
3
+
4
+ describe("resolveSlideSurfaceClassName", () => {
5
+ it("includes base classes for a default layout", () => {
6
+ const result = resolveSlideSurfaceClassName({ layout: "default" });
7
+ expect(result).toContain("slide-prose");
8
+ expect(result).toContain("slide-surface-frame");
9
+ });
10
+
11
+ it("uses zero padding for immersive layout", () => {
12
+ const result = resolveSlideSurfaceClassName({ layout: "immersive" });
13
+ expect(result).toContain("px-0 py-0");
14
+ expect(result).not.toContain("slide-surface-frame");
15
+ });
16
+
17
+ it("includes overflow-hidden when requested", () => {
18
+ const result = resolveSlideSurfaceClassName({
19
+ layout: "default",
20
+ overflowHidden: true,
21
+ });
22
+ expect(result).toContain("overflow-hidden");
23
+ });
24
+
25
+ it("omits overflow-hidden by default", () => {
26
+ const result = resolveSlideSurfaceClassName({ layout: "default" });
27
+ expect(result).not.toContain("overflow-hidden");
28
+ });
29
+
30
+ it("appends custom shadow class", () => {
31
+ const result = resolveSlideSurfaceClassName({
32
+ layout: "default",
33
+ shadowClass: "shadow-lg",
34
+ });
35
+ expect(result).toContain("shadow-lg");
36
+ });
37
+ });
38
+
39
+ describe("resolveSlideSurface", () => {
40
+ it("returns default white background when no background is set", () => {
41
+ const result = resolveSlideSurface({
42
+ meta: { layout: "default" },
43
+ });
44
+ expect(result.style.backgroundColor).toBe("#ffffff");
45
+ });
46
+
47
+ it("detects bare image URLs and sets backgroundImage", () => {
48
+ const result = resolveSlideSurface({
49
+ meta: { layout: "default", background: "https://example.com/bg.jpg" },
50
+ });
51
+ expect(result.style.backgroundImage).toContain("url(");
52
+ expect(result.style.backgroundSize).toBe("cover");
53
+ });
54
+
55
+ it("detects relative image paths", () => {
56
+ const result = resolveSlideSurface({
57
+ meta: { layout: "default", background: "./images/bg.png" },
58
+ });
59
+ expect(result.style.backgroundImage).toContain("url(");
60
+ });
61
+
62
+ it("detects data URI images", () => {
63
+ const result = resolveSlideSurface({
64
+ meta: { layout: "default", background: "data:image/svg+xml;base64,abc" },
65
+ });
66
+ expect(result.style.backgroundImage).toContain("url(");
67
+ });
68
+
69
+ it("treats CSS values as raw background property", () => {
70
+ const result = resolveSlideSurface({
71
+ meta: { layout: "default", background: "linear-gradient(to right, #000, #fff)" },
72
+ });
73
+ expect(result.style.background).toContain("linear-gradient");
74
+ expect(result.style.backgroundImage).toBeUndefined();
75
+ });
76
+
77
+ it("falls back to slidesBackground when slide has no background", () => {
78
+ const result = resolveSlideSurface({
79
+ meta: { layout: "default" },
80
+ slidesBackground: "#333",
81
+ });
82
+ expect(result.style.background).toBe("#333");
83
+ });
84
+
85
+ it("merges meta.class into className", () => {
86
+ const result = resolveSlideSurface({
87
+ meta: { layout: "default", class: "custom-slide" },
88
+ className: "base-class",
89
+ });
90
+ expect(result.className).toContain("base-class");
91
+ expect(result.className).toContain("custom-slide");
92
+ });
93
+ });
@@ -1,11 +1,11 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { describe, expect, it } from "vite-plus/test";
2
2
  import {
3
3
  countPeers,
4
4
  markPeerSeen,
5
5
  removePeer,
6
6
  resolveRemoteActive,
7
7
  sweepStalePeers,
8
- } from "./presence";
8
+ } from "../presence";
9
9
 
10
10
  describe("presence model", () => {
11
11
  it("tracks peer activity and counts peers", () => {
@@ -1,4 +1,4 @@
1
- import { describe, expect, it, vi } from "vitest";
1
+ import { describe, expect, it, vi } from "vite-plus/test";
2
2
  import {
3
3
  canAuthorState,
4
4
  canReceive,
@@ -6,7 +6,7 @@ import {
6
6
  createEnvelope,
7
7
  createSnapshotState,
8
8
  isCursorEqual,
9
- } from "./replication";
9
+ } from "../replication";
10
10
 
11
11
  describe("replication model", () => {
12
12
  it("creates join envelopes with protocol metadata", () => {
@@ -1,5 +1,5 @@
1
- import { describe, expect, it } from "vitest";
2
- import { resolvePresentationSyncStatus } from "./status";
1
+ import { describe, expect, it } from "vite-plus/test";
2
+ import { resolvePresentationSyncStatus } from "../status";
3
3
 
4
4
  describe("resolvePresentationSyncStatus", () => {
5
5
  it("returns disabled when the session is disabled", () => {
@@ -1,10 +1,10 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { describe, expect, it } from "vite-plus/test";
2
2
  import {
3
3
  DEFAULT_SLIDES_VIEWPORT,
4
4
  resolveSlidesViewportMeta,
5
5
  } from "@slidev-react/core/slides/viewport";
6
- import { resolveSlideTheme } from "./registry";
7
- import { resolveThemeRootAttributes } from "./ThemeProvider";
6
+ import { resolveSlideTheme } from "../registry";
7
+ import { resolveThemeRootAttributes } from "../ThemeProvider";
8
8
 
9
9
  describe("ThemeProvider root attributes", () => {
10
10
  it("keeps theme attributes when no viewport is provided", () => {
@@ -1,7 +1,7 @@
1
- import { describe, expect, it } from "vitest";
2
- import { Badge } from "../ui/primitives/Badge";
3
- import { defaultLayouts } from "./layouts/defaultLayouts";
4
- import { resolveSlideTheme } from "./registry";
1
+ import { describe, expect, it } from "vite-plus/test";
2
+ import { Badge } from "../../ui/primitives/Badge";
3
+ import { defaultLayouts } from "../layouts/defaultLayouts";
4
+ import { resolveSlideTheme } from "../registry";
5
5
 
6
6
  describe("theme registry", () => {
7
7
  it("falls back to the default theme when no active theme is set", () => {
@@ -0,0 +1,31 @@
1
+ import { describe, expect, it } from "vite-plus/test";
2
+ import { defaultLayouts } from "../defaultLayouts";
3
+ import { resolveLayout } from "../resolveLayout";
4
+
5
+ describe("resolveLayout", () => {
6
+ it("returns the default layout when layout is undefined", () => {
7
+ expect(resolveLayout(undefined)).toBe(defaultLayouts.default);
8
+ });
9
+
10
+ it("returns the matching layout from the default registry", () => {
11
+ expect(resolveLayout("cover")).toBe(defaultLayouts.cover);
12
+ });
13
+
14
+ it("falls back to default layout for an unknown layout name", () => {
15
+ expect(resolveLayout("nonexistent")).toBe(defaultLayouts.default);
16
+ });
17
+
18
+ it("uses a custom registry when provided", () => {
19
+ const customDefault = () => null;
20
+ const customCover = () => null;
21
+ const registry = { default: customDefault, cover: customCover };
22
+
23
+ expect(resolveLayout("cover", registry)).toBe(customCover);
24
+ expect(resolveLayout(undefined, registry)).toBe(customDefault);
25
+ });
26
+
27
+ it("falls back to defaultLayouts.default when custom registry has no default", () => {
28
+ const registry = { cover: () => null };
29
+ expect(resolveLayout("missing", registry)).toBe(defaultLayouts.default);
30
+ });
31
+ });
@@ -1,10 +1,10 @@
1
1
  import { renderToStaticMarkup } from "react-dom/server";
2
- import { describe, expect, it, vi } from "vitest";
2
+ import { describe, expect, it, vi } from "vite-plus/test";
3
3
  import {
4
4
  RevealProvider,
5
5
  type RevealContextValue,
6
- } from "../../features/presentation/reveal/RevealContext";
7
- import { Annotate } from "./Annotate";
6
+ } from "../../../features/presentation/reveal/RevealContext";
7
+ import { Annotate } from "../Annotate";
8
8
 
9
9
  function createRevealValue(clicks: number): RevealContextValue {
10
10
  return {