@btst/stack 2.8.1 → 2.9.1
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/README.md +3 -2
- package/dist/components/markdown/index.d.cts +15 -2
- package/dist/components/markdown/index.d.mts +15 -2
- package/dist/components/markdown/index.d.ts +15 -2
- package/dist/packages/stack/src/plugins/blog/client/components/forms/image-field.cjs +30 -1
- package/dist/packages/stack/src/plugins/blog/client/components/forms/image-field.mjs +30 -1
- package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.cjs +49 -9
- package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.mjs +50 -10
- package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor.cjs +77 -9
- package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor.mjs +77 -9
- package/dist/packages/stack/src/plugins/cms/client/components/forms/content-form.cjs +24 -5
- package/dist/packages/stack/src/plugins/cms/client/components/forms/content-form.mjs +24 -5
- package/dist/packages/stack/src/plugins/cms/client/components/forms/file-upload.cjs +47 -13
- package/dist/packages/stack/src/plugins/cms/client/components/forms/file-upload.mjs +47 -13
- package/dist/packages/stack/src/plugins/kanban/client/components/forms/board-form.cjs +1 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/forms/board-form.mjs +1 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.cjs +6 -2
- package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.mjs +6 -2
- package/dist/packages/stack/src/plugins/media/api/adapters/local.cjs +55 -0
- package/dist/packages/stack/src/plugins/media/api/adapters/local.mjs +37 -0
- package/dist/packages/stack/src/plugins/media/api/getters.cjs +83 -0
- package/dist/packages/stack/src/plugins/media/api/getters.mjs +78 -0
- package/dist/packages/stack/src/plugins/media/api/mutations.cjs +88 -0
- package/dist/packages/stack/src/plugins/media/api/mutations.mjs +82 -0
- package/dist/packages/stack/src/plugins/media/api/plugin.cjs +525 -0
- package/dist/packages/stack/src/plugins/media/api/plugin.mjs +523 -0
- package/dist/packages/stack/src/plugins/media/api/query-key-defs.cjs +19 -0
- package/dist/packages/stack/src/plugins/media/api/query-key-defs.mjs +16 -0
- package/dist/packages/stack/src/plugins/media/api/serializers.cjs +17 -0
- package/dist/packages/stack/src/plugins/media/api/serializers.mjs +14 -0
- package/dist/packages/stack/src/plugins/media/api/storage-adapter.cjs +15 -0
- package/dist/packages/stack/src/plugins/media/api/storage-adapter.mjs +11 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-card.cjs +129 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-card.mjs +127 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-preview-button.cjs +58 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-preview-button.mjs +56 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/browse-tab.cjs +94 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/browse-tab.mjs +92 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/folder-tree.cjs +171 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/folder-tree.mjs +168 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/index.cjs +308 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/index.mjs +305 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/upload-tab.cjs +104 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/upload-tab.mjs +102 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/url-tab.cjs +70 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/url-tab.mjs +68 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/utils.cjs +21 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/utils.mjs +17 -0
- package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.cjs +35 -0
- package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.internal.cjs +125 -0
- package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.internal.mjs +123 -0
- package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.mjs +33 -0
- package/dist/packages/stack/src/plugins/media/client/hooks/use-media.cjs +222 -0
- package/dist/packages/stack/src/plugins/media/client/hooks/use-media.mjs +214 -0
- package/dist/packages/stack/src/plugins/media/client/plugin.cjs +94 -0
- package/dist/packages/stack/src/plugins/media/client/plugin.mjs +92 -0
- package/dist/packages/stack/src/plugins/media/client/upload.cjs +121 -0
- package/dist/packages/stack/src/plugins/media/client/upload.mjs +119 -0
- package/dist/packages/stack/src/plugins/media/client/utils/image-compression.cjs +67 -0
- package/dist/packages/stack/src/plugins/media/client/utils/image-compression.mjs +65 -0
- package/dist/packages/stack/src/plugins/media/db.cjs +62 -0
- package/dist/packages/stack/src/plugins/media/db.mjs +60 -0
- package/dist/packages/stack/src/plugins/media/schemas.cjs +41 -0
- package/dist/packages/stack/src/plugins/media/schemas.mjs +35 -0
- package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-block.cjs +18 -1
- package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-block.mjs +19 -2
- package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-dialog.cjs +2 -2
- package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-dialog.mjs +2 -2
- package/dist/packages/ui/src/components/minimal-tiptap/components/section/five.cjs +3 -2
- package/dist/packages/ui/src/components/minimal-tiptap/components/section/five.mjs +3 -2
- package/dist/packages/ui/src/components/minimal-tiptap/minimal-tiptap.cjs +12 -5
- package/dist/packages/ui/src/components/minimal-tiptap/minimal-tiptap.mjs +12 -5
- package/dist/plugins/blog/client/index.d.cts +58 -1
- package/dist/plugins/blog/client/index.d.mts +58 -1
- package/dist/plugins/blog/client/index.d.ts +58 -1
- package/dist/plugins/cms/client/index.d.cts +73 -3
- package/dist/plugins/cms/client/index.d.mts +73 -3
- package/dist/plugins/cms/client/index.d.ts +73 -3
- package/dist/plugins/kanban/api/index.d.cts +1 -1
- package/dist/plugins/kanban/api/index.d.mts +1 -1
- package/dist/plugins/kanban/api/index.d.ts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
- package/dist/plugins/kanban/client/index.d.cts +1 -1
- package/dist/plugins/kanban/client/index.d.mts +1 -1
- package/dist/plugins/kanban/client/index.d.ts +1 -1
- package/dist/plugins/kanban/query-keys.d.cts +1 -1
- package/dist/plugins/kanban/query-keys.d.mts +1 -1
- package/dist/plugins/kanban/query-keys.d.ts +1 -1
- package/dist/plugins/media/api/adapters/s3.cjs +106 -0
- package/dist/plugins/media/api/adapters/s3.d.cts +60 -0
- package/dist/plugins/media/api/adapters/s3.d.mts +60 -0
- package/dist/plugins/media/api/adapters/s3.d.ts +60 -0
- package/dist/plugins/media/api/adapters/s3.mjs +104 -0
- package/dist/plugins/media/api/adapters/vercel-blob.cjs +53 -0
- package/dist/plugins/media/api/adapters/vercel-blob.d.cts +41 -0
- package/dist/plugins/media/api/adapters/vercel-blob.d.mts +41 -0
- package/dist/plugins/media/api/adapters/vercel-blob.d.ts +41 -0
- package/dist/plugins/media/api/adapters/vercel-blob.mjs +51 -0
- package/dist/plugins/media/api/index.cjs +26 -0
- package/dist/plugins/media/api/index.d.cts +116 -0
- package/dist/plugins/media/api/index.d.mts +116 -0
- package/dist/plugins/media/api/index.d.ts +116 -0
- package/dist/plugins/media/api/index.mjs +6 -0
- package/dist/plugins/media/client/components/index.cjs +10 -0
- package/dist/plugins/media/client/components/index.d.cts +55 -0
- package/dist/plugins/media/client/components/index.d.mts +55 -0
- package/dist/plugins/media/client/components/index.d.ts +55 -0
- package/dist/plugins/media/client/components/index.mjs +2 -0
- package/dist/plugins/media/client/hooks/index.cjs +13 -0
- package/dist/plugins/media/client/hooks/index.d.cts +53 -0
- package/dist/plugins/media/client/hooks/index.d.mts +53 -0
- package/dist/plugins/media/client/hooks/index.d.ts +53 -0
- package/dist/plugins/media/client/hooks/index.mjs +1 -0
- package/dist/plugins/media/client/index.cjs +9 -0
- package/dist/plugins/media/client/index.d.cts +242 -0
- package/dist/plugins/media/client/index.d.mts +242 -0
- package/dist/plugins/media/client/index.d.ts +242 -0
- package/dist/plugins/media/client/index.mjs +2 -0
- package/dist/plugins/media/client.css +1 -0
- package/dist/plugins/media/query-keys.cjs +72 -0
- package/dist/plugins/media/query-keys.d.cts +49 -0
- package/dist/plugins/media/query-keys.d.mts +49 -0
- package/dist/plugins/media/query-keys.d.ts +49 -0
- package/dist/plugins/media/query-keys.mjs +70 -0
- package/dist/plugins/media/style.css +1 -0
- package/dist/shared/{stack.DRpeDS6X.d.ts → stack.BMx2QYOK.d.ts} +25 -0
- package/dist/shared/stack.BttDsJJn.d.cts +109 -0
- package/dist/shared/stack.BttDsJJn.d.mts +109 -0
- package/dist/shared/stack.BttDsJJn.d.ts +109 -0
- package/dist/shared/stack.C7vfOBmO.d.mts +63 -0
- package/dist/shared/stack.CAni8dnD.d.cts +63 -0
- package/dist/shared/stack.CI8iRKKi.d.cts +286 -0
- package/dist/shared/stack.CLcnSF_b.d.cts +25 -0
- package/dist/shared/stack.CLcnSF_b.d.mts +25 -0
- package/dist/shared/stack.CLcnSF_b.d.ts +25 -0
- package/dist/shared/stack.CYSwntXC.d.ts +63 -0
- package/dist/shared/{stack.Jb0kQDJC.d.mts → stack.Cd6McBu1.d.mts} +25 -0
- package/dist/shared/stack.DJDjdG64.d.ts +286 -0
- package/dist/shared/{stack.BxFl46lB.d.cts → stack.DxQl8Wa1.d.cts} +25 -0
- package/dist/shared/stack.FgBVDSPi.d.mts +286 -0
- package/package.json +113 -4
- package/src/plugins/blog/client/components/forms/image-field.tsx +35 -4
- package/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.tsx +67 -12
- package/src/plugins/blog/client/components/forms/markdown-editor.tsx +106 -10
- package/src/plugins/blog/client/overrides.ts +58 -1
- package/src/plugins/cms/client/components/forms/content-form.tsx +26 -7
- package/src/plugins/cms/client/components/forms/file-upload.tsx +73 -15
- package/src/plugins/cms/client/overrides.ts +57 -2
- package/src/plugins/kanban/client/components/forms/board-form.tsx +1 -1
- package/src/plugins/kanban/client/components/forms/task-form.tsx +7 -1
- package/src/plugins/kanban/client/overrides.ts +25 -0
- package/src/plugins/media/__tests__/__stubs__/vercel-blob-server.ts +9 -0
- package/src/plugins/media/__tests__/getters.test.ts +274 -0
- package/src/plugins/media/__tests__/mutations.test.ts +299 -0
- package/src/plugins/media/__tests__/plugin.test.ts +752 -0
- package/src/plugins/media/__tests__/query-key-defs.test.ts +54 -0
- package/src/plugins/media/__tests__/storage-adapters.test.ts +351 -0
- package/src/plugins/media/api/adapters/local.ts +79 -0
- package/src/plugins/media/api/adapters/s3.ts +198 -0
- package/src/plugins/media/api/adapters/vercel-blob.ts +131 -0
- package/src/plugins/media/api/getters.ts +174 -0
- package/src/plugins/media/api/index.ts +41 -0
- package/src/plugins/media/api/mutations.ts +179 -0
- package/src/plugins/media/api/plugin.ts +855 -0
- package/src/plugins/media/api/query-key-defs.ts +41 -0
- package/src/plugins/media/api/serializers.ts +28 -0
- package/src/plugins/media/api/storage-adapter.ts +139 -0
- package/src/plugins/media/client/components/index.tsx +6 -0
- package/src/plugins/media/client/components/media-picker/asset-card.tsx +150 -0
- package/src/plugins/media/client/components/media-picker/asset-preview-button.tsx +67 -0
- package/src/plugins/media/client/components/media-picker/browse-tab.tsx +116 -0
- package/src/plugins/media/client/components/media-picker/folder-tree.tsx +188 -0
- package/src/plugins/media/client/components/media-picker/index.tsx +347 -0
- package/src/plugins/media/client/components/media-picker/upload-tab.tsx +108 -0
- package/src/plugins/media/client/components/media-picker/url-tab.tsx +72 -0
- package/src/plugins/media/client/components/media-picker/utils.ts +17 -0
- package/src/plugins/media/client/components/pages/library-page.internal.tsx +134 -0
- package/src/plugins/media/client/components/pages/library-page.tsx +42 -0
- package/src/plugins/media/client/hooks/index.tsx +9 -0
- package/src/plugins/media/client/hooks/use-media.tsx +289 -0
- package/src/plugins/media/client/index.ts +4 -0
- package/src/plugins/media/client/overrides.ts +127 -0
- package/src/plugins/media/client/plugin.tsx +184 -0
- package/src/plugins/media/client/upload.ts +171 -0
- package/src/plugins/media/client/utils/image-compression.ts +131 -0
- package/src/plugins/media/client.css +1 -0
- package/src/plugins/media/db.ts +62 -0
- package/src/plugins/media/query-keys.ts +96 -0
- package/src/plugins/media/schemas.ts +37 -0
- package/src/plugins/media/style.css +1 -0
- package/src/plugins/media/types.ts +26 -0
- package/dist/shared/{stack.BOokfhZD.d.cts → stack.B6S3cgwN.d.cts} +16 -16
- package/dist/shared/{stack.CWxAl9K3.d.mts → stack.Bzfx-_lq.d.mts} +16 -16
- package/dist/shared/{stack.BvCR4-9H.d.ts → stack.j5SFLC1d.d.ts} +16 -16
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import { createMemoryAdapter } from "@btst/adapter-memory";
|
|
3
|
+
import { defineDb } from "@btst/db";
|
|
4
|
+
import type { DBAdapter as Adapter } from "@btst/db";
|
|
5
|
+
import { mediaSchema } from "../db";
|
|
6
|
+
import type { Asset, Folder } from "../types";
|
|
7
|
+
import {
|
|
8
|
+
createAsset,
|
|
9
|
+
updateAsset,
|
|
10
|
+
deleteAsset,
|
|
11
|
+
createFolder,
|
|
12
|
+
deleteFolder,
|
|
13
|
+
} from "../api/mutations";
|
|
14
|
+
import { getAssetById, getFolderById } from "../api/getters";
|
|
15
|
+
|
|
16
|
+
const createTestAdapter = (): Adapter => {
|
|
17
|
+
const db = defineDb({}).use(mediaSchema);
|
|
18
|
+
return createMemoryAdapter(db)({});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const assetInput = {
|
|
22
|
+
filename: "photo.jpg",
|
|
23
|
+
originalName: "My Photo.jpg",
|
|
24
|
+
mimeType: "image/jpeg",
|
|
25
|
+
size: 2048,
|
|
26
|
+
url: "https://example.com/photo.jpg",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
describe("media mutations", () => {
|
|
30
|
+
let adapter: Adapter;
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
adapter = createTestAdapter();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// ── createAsset ───────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
describe("createAsset", () => {
|
|
39
|
+
it("creates an asset with required fields", async () => {
|
|
40
|
+
const asset = await createAsset(adapter, assetInput);
|
|
41
|
+
|
|
42
|
+
expect(asset.id).toBeDefined();
|
|
43
|
+
expect(asset.filename).toBe("photo.jpg");
|
|
44
|
+
expect(asset.originalName).toBe("My Photo.jpg");
|
|
45
|
+
expect(asset.mimeType).toBe("image/jpeg");
|
|
46
|
+
expect(asset.size).toBe(2048);
|
|
47
|
+
expect(asset.url).toBe("https://example.com/photo.jpg");
|
|
48
|
+
expect(asset.createdAt).toBeInstanceOf(Date);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("creates an asset with optional fields", async () => {
|
|
52
|
+
const asset = await createAsset(adapter, {
|
|
53
|
+
...assetInput,
|
|
54
|
+
folderId: "folder-123",
|
|
55
|
+
alt: "A beautiful photo",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(asset.folderId).toBe("folder-123");
|
|
59
|
+
expect(asset.alt).toBe("A beautiful photo");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("creates multiple independent assets", async () => {
|
|
63
|
+
await createAsset(adapter, {
|
|
64
|
+
...assetInput,
|
|
65
|
+
filename: "a.jpg",
|
|
66
|
+
url: "https://example.com/a.jpg",
|
|
67
|
+
});
|
|
68
|
+
await createAsset(adapter, {
|
|
69
|
+
...assetInput,
|
|
70
|
+
filename: "b.jpg",
|
|
71
|
+
url: "https://example.com/b.jpg",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const all = await adapter.findMany<Asset>({ model: "mediaAsset" });
|
|
75
|
+
expect(all).toHaveLength(2);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ── updateAsset ───────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
describe("updateAsset", () => {
|
|
82
|
+
it("updates the alt text of an asset", async () => {
|
|
83
|
+
const asset = await createAsset(adapter, assetInput);
|
|
84
|
+
const updated = await updateAsset(adapter, asset.id, {
|
|
85
|
+
alt: "Updated alt text",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(updated).not.toBeNull();
|
|
89
|
+
expect(updated!.alt).toBe("Updated alt text");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("updates the folderId of an asset", async () => {
|
|
93
|
+
const asset = await createAsset(adapter, assetInput);
|
|
94
|
+
const updated = await updateAsset(adapter, asset.id, {
|
|
95
|
+
folderId: "folder-abc",
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(updated!.folderId).toBe("folder-abc");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("returns the updated asset when only folderId is changed", async () => {
|
|
102
|
+
const asset = await createAsset(adapter, {
|
|
103
|
+
...assetInput,
|
|
104
|
+
folderId: "folder-old",
|
|
105
|
+
});
|
|
106
|
+
const updated = await updateAsset(adapter, asset.id, {
|
|
107
|
+
folderId: "folder-new",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
expect(updated).not.toBeNull();
|
|
111
|
+
expect(updated!.folderId).toBe("folder-new");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("clears the folder association when folderId is null", async () => {
|
|
115
|
+
const asset = await createAsset(adapter, {
|
|
116
|
+
...assetInput,
|
|
117
|
+
folderId: "folder-to-clear",
|
|
118
|
+
});
|
|
119
|
+
expect(asset.folderId).toBe("folder-to-clear");
|
|
120
|
+
|
|
121
|
+
const updated = await updateAsset(adapter, asset.id, { folderId: null });
|
|
122
|
+
expect(updated).not.toBeNull();
|
|
123
|
+
expect(updated!.folderId == null).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("does not change folderId when folderId is not in the input", async () => {
|
|
127
|
+
const asset = await createAsset(adapter, {
|
|
128
|
+
...assetInput,
|
|
129
|
+
folderId: "folder-keep",
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const updated = await updateAsset(adapter, asset.id, {
|
|
133
|
+
alt: "new alt text",
|
|
134
|
+
});
|
|
135
|
+
expect(updated).not.toBeNull();
|
|
136
|
+
expect(updated!.folderId).toBe("folder-keep");
|
|
137
|
+
expect(updated!.alt).toBe("new alt text");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("returns null for nonexistent asset", async () => {
|
|
141
|
+
const result = await updateAsset(adapter, "nonexistent-id", {
|
|
142
|
+
alt: "test",
|
|
143
|
+
});
|
|
144
|
+
expect(result).toBeNull();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// ── deleteAsset ───────────────────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
describe("deleteAsset", () => {
|
|
151
|
+
it("removes the asset from the database", async () => {
|
|
152
|
+
const asset = await createAsset(adapter, assetInput);
|
|
153
|
+
await deleteAsset(adapter, asset.id);
|
|
154
|
+
|
|
155
|
+
const found = await getAssetById(adapter, asset.id);
|
|
156
|
+
expect(found).toBeNull();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("does not throw when deleting a nonexistent asset", async () => {
|
|
160
|
+
await expect(
|
|
161
|
+
deleteAsset(adapter, "nonexistent-id"),
|
|
162
|
+
).resolves.not.toThrow();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("only deletes the targeted asset", async () => {
|
|
166
|
+
const a = await createAsset(adapter, {
|
|
167
|
+
...assetInput,
|
|
168
|
+
filename: "a.jpg",
|
|
169
|
+
url: "https://example.com/a.jpg",
|
|
170
|
+
});
|
|
171
|
+
await createAsset(adapter, {
|
|
172
|
+
...assetInput,
|
|
173
|
+
filename: "b.jpg",
|
|
174
|
+
url: "https://example.com/b.jpg",
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await deleteAsset(adapter, a.id);
|
|
178
|
+
|
|
179
|
+
const remaining = await adapter.findMany<Asset>({ model: "mediaAsset" });
|
|
180
|
+
expect(remaining).toHaveLength(1);
|
|
181
|
+
expect(remaining[0]!.filename).toBe("b.jpg");
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// ── createFolder ──────────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
describe("createFolder", () => {
|
|
188
|
+
it("creates a root folder", async () => {
|
|
189
|
+
const folder = await createFolder(adapter, { name: "Uploads" });
|
|
190
|
+
|
|
191
|
+
expect(folder.id).toBeDefined();
|
|
192
|
+
expect(folder.name).toBe("Uploads");
|
|
193
|
+
expect(folder.parentId).toBeUndefined();
|
|
194
|
+
expect(folder.createdAt).toBeInstanceOf(Date);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("creates a nested folder with parentId", async () => {
|
|
198
|
+
const parent = await createFolder(adapter, { name: "Root" });
|
|
199
|
+
const child = await createFolder(adapter, {
|
|
200
|
+
name: "Photos",
|
|
201
|
+
parentId: parent.id,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
expect(child.parentId).toBe(parent.id);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// ── deleteFolder ──────────────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
describe("deleteFolder", () => {
|
|
211
|
+
it("deletes an empty folder", async () => {
|
|
212
|
+
const folder = await createFolder(adapter, { name: "Empty" });
|
|
213
|
+
await deleteFolder(adapter, folder.id);
|
|
214
|
+
|
|
215
|
+
const found = await getFolderById(adapter, folder.id);
|
|
216
|
+
expect(found).toBeNull();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("throws an error when the folder contains assets", async () => {
|
|
220
|
+
const folder = await createFolder(adapter, { name: "Full Folder" });
|
|
221
|
+
await createAsset(adapter, { ...assetInput, folderId: folder.id });
|
|
222
|
+
|
|
223
|
+
await expect(deleteFolder(adapter, folder.id)).rejects.toThrow(
|
|
224
|
+
"Cannot delete folder",
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const stillExists = await getFolderById(adapter, folder.id);
|
|
228
|
+
expect(stillExists).not.toBeNull();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("allows deletion after assets are removed", async () => {
|
|
232
|
+
const folder = await createFolder(adapter, { name: "Soon Empty" });
|
|
233
|
+
const asset = await createAsset(adapter, {
|
|
234
|
+
...assetInput,
|
|
235
|
+
folderId: folder.id,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
await deleteAsset(adapter, asset.id);
|
|
239
|
+
await expect(deleteFolder(adapter, folder.id)).resolves.not.toThrow();
|
|
240
|
+
|
|
241
|
+
const found = await getFolderById(adapter, folder.id);
|
|
242
|
+
expect(found).toBeNull();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("does not throw when deleting a nonexistent folder", async () => {
|
|
246
|
+
await expect(
|
|
247
|
+
deleteFolder(adapter, "nonexistent-id"),
|
|
248
|
+
).resolves.not.toThrow();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("only deletes the targeted folder and not siblings", async () => {
|
|
252
|
+
const a = await createFolder(adapter, { name: "Folder A" });
|
|
253
|
+
await createFolder(adapter, { name: "Folder B" });
|
|
254
|
+
|
|
255
|
+
await deleteFolder(adapter, a.id);
|
|
256
|
+
|
|
257
|
+
const remaining = await adapter.findMany<Folder>({
|
|
258
|
+
model: "mediaFolder",
|
|
259
|
+
});
|
|
260
|
+
expect(remaining).toHaveLength(1);
|
|
261
|
+
expect(remaining[0]!.name).toBe("Folder B");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("cascade-deletes child folders when the subtree has no assets", async () => {
|
|
265
|
+
const parent = await createFolder(adapter, { name: "Parent" });
|
|
266
|
+
const child = await createFolder(adapter, {
|
|
267
|
+
name: "Child",
|
|
268
|
+
parentId: parent.id,
|
|
269
|
+
});
|
|
270
|
+
const grandchild = await createFolder(adapter, {
|
|
271
|
+
name: "Grandchild",
|
|
272
|
+
parentId: child.id,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
await deleteFolder(adapter, parent.id);
|
|
276
|
+
|
|
277
|
+
expect(await getFolderById(adapter, parent.id)).toBeNull();
|
|
278
|
+
expect(await getFolderById(adapter, child.id)).toBeNull();
|
|
279
|
+
expect(await getFolderById(adapter, grandchild.id)).toBeNull();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("throws when a child folder contains assets and leaves the whole subtree intact", async () => {
|
|
283
|
+
const parent = await createFolder(adapter, { name: "Parent" });
|
|
284
|
+
const child = await createFolder(adapter, {
|
|
285
|
+
name: "Child",
|
|
286
|
+
parentId: parent.id,
|
|
287
|
+
});
|
|
288
|
+
await createAsset(adapter, { ...assetInput, folderId: child.id });
|
|
289
|
+
|
|
290
|
+
await expect(deleteFolder(adapter, parent.id)).rejects.toThrow(
|
|
291
|
+
"Cannot delete folder",
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
// Both folders still exist — no partial deletion.
|
|
295
|
+
expect(await getFolderById(adapter, parent.id)).not.toBeNull();
|
|
296
|
+
expect(await getFolderById(adapter, child.id)).not.toBeNull();
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
});
|