@cosmicdrift/kumiko-bundled-features 0.90.3 → 0.92.0
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/package.json +7 -6
- package/src/folders/web/__tests__/folder-manager.test.tsx +168 -0
- package/src/folders/web/folder-manager.tsx +286 -73
- package/src/folders/web/i18n.ts +6 -0
- package/src/folders/web/index.ts +1 -1
- package/src/ledger/__tests__/feature.test.ts +164 -0
- package/src/ledger/__tests__/ledger.integration.test.ts +300 -0
- package/src/ledger/__tests__/reports.test.ts +122 -0
- package/src/ledger/constants.ts +47 -0
- package/src/ledger/entity.ts +54 -0
- package/src/ledger/executor.ts +14 -0
- package/src/ledger/feature.ts +109 -0
- package/src/ledger/handlers/create-transaction.write.ts +44 -0
- package/src/ledger/handlers/reports.query.ts +79 -0
- package/src/ledger/handlers/reverse-transaction.write.ts +48 -0
- package/src/ledger/index.ts +52 -0
- package/src/ledger/reports.ts +164 -0
- package/src/ledger/schemas.ts +51 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-bundled-features",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.92.0",
|
|
4
4
|
"description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"./folders": "./src/folders/index.ts",
|
|
36
36
|
"./folders/web": "./src/folders/web/index.ts",
|
|
37
37
|
"./folders-user-data": "./src/folders-user-data/index.ts",
|
|
38
|
+
"./ledger": "./src/ledger/index.ts",
|
|
38
39
|
"./billing-foundation": "./src/billing-foundation/index.ts",
|
|
39
40
|
"./subscription-stripe": "./src/subscription-stripe/index.ts",
|
|
40
41
|
"./subscription-mollie": "./src/subscription-mollie/index.ts",
|
|
@@ -89,11 +90,11 @@
|
|
|
89
90
|
"./step-dispatcher": "./src/step-dispatcher/index.ts"
|
|
90
91
|
},
|
|
91
92
|
"dependencies": {
|
|
92
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.
|
|
93
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
94
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
95
|
-
"@cosmicdrift/kumiko-renderer": "0.
|
|
96
|
-
"@cosmicdrift/kumiko-renderer-web": "0.
|
|
93
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.92.0",
|
|
94
|
+
"@cosmicdrift/kumiko-framework": "0.92.0",
|
|
95
|
+
"@cosmicdrift/kumiko-headless": "0.92.0",
|
|
96
|
+
"@cosmicdrift/kumiko-renderer": "0.92.0",
|
|
97
|
+
"@cosmicdrift/kumiko-renderer-web": "0.92.0",
|
|
97
98
|
"@mollie/api-client": "^4.5.0",
|
|
98
99
|
"@node-rs/argon2": "^2.0.2",
|
|
99
100
|
"@types/nodemailer": "^8.0.0",
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
createStaticLocaleResolver,
|
|
4
|
+
LocaleProvider,
|
|
5
|
+
PrimitivesProvider,
|
|
6
|
+
} from "@cosmicdrift/kumiko-renderer";
|
|
7
|
+
import { defaultPrimitives } from "@cosmicdrift/kumiko-renderer-web";
|
|
8
|
+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
9
|
+
import type { ReactNode } from "react";
|
|
10
|
+
import { FoldersHandlers, FoldersQueries } from "../../constants";
|
|
11
|
+
import { type FolderFiling, FolderManager } from "../folder-manager";
|
|
12
|
+
import { defaultTranslations } from "../i18n";
|
|
13
|
+
|
|
14
|
+
type FolderRow = { id: string; name: string; parentId: string | null; version: number };
|
|
15
|
+
|
|
16
|
+
let folderRows: readonly FolderRow[] = [];
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
folderRows = [];
|
|
20
|
+
dispatchSpy.mockClear();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const dispatchSpy = mock(async () => ({ isSuccess: true, data: undefined }));
|
|
24
|
+
|
|
25
|
+
const useQuerySpy = mock((type: string) => ({
|
|
26
|
+
data: type === FoldersQueries.folderList ? { rows: folderRows } : { rows: [] },
|
|
27
|
+
loading: false,
|
|
28
|
+
error: null,
|
|
29
|
+
refetch: mock(async () => {}),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
const actual_renderer = await import("@cosmicdrift/kumiko-renderer");
|
|
33
|
+
mock.module("@cosmicdrift/kumiko-renderer", () => ({
|
|
34
|
+
...actual_renderer,
|
|
35
|
+
useDispatcher: mock(() => ({ write: dispatchSpy, query: mock(), batch: mock() })),
|
|
36
|
+
useQuery: useQuerySpy,
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
function Wrapper({ children }: { readonly children: ReactNode }): ReactNode {
|
|
40
|
+
return (
|
|
41
|
+
<LocaleProvider resolver={createStaticLocaleResolver()} fallbackBundles={[defaultTranslations]}>
|
|
42
|
+
<PrimitivesProvider value={defaultPrimitives}>{children}</PrimitivesProvider>
|
|
43
|
+
</LocaleProvider>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const filingWith = (onReassigned: () => void): FolderFiling => ({
|
|
48
|
+
entityType: "credit",
|
|
49
|
+
leavesByFolder: new Map([["f1", [{ id: "c-1", label: "Credit 1" }]]]),
|
|
50
|
+
unfiled: [{ id: "c-2", label: "Credit 2" }],
|
|
51
|
+
unfiledLabel: "Unfiled",
|
|
52
|
+
onReassigned,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const dropLeaf = (targetTestId: string, entityId: string): void => {
|
|
56
|
+
fireEvent.drop(screen.getByTestId(targetTestId), {
|
|
57
|
+
dataTransfer: { getData: () => entityId },
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
describe("FolderManager filing mode", () => {
|
|
62
|
+
test("renders filed leaves under their folder and the unfiled bucket", () => {
|
|
63
|
+
folderRows = [{ id: "f1", name: "A", parentId: null, version: 1 }];
|
|
64
|
+
render(
|
|
65
|
+
<Wrapper>
|
|
66
|
+
<FolderManager filing={filingWith(() => {})} />
|
|
67
|
+
</Wrapper>,
|
|
68
|
+
);
|
|
69
|
+
expect(screen.getByTestId("folder-leaf-c-1")).toBeTruthy();
|
|
70
|
+
expect(screen.getByTestId("folder-node-unfiled")).toBeTruthy();
|
|
71
|
+
expect(screen.getByTestId("folder-leaf-c-2")).toBeTruthy();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("dropping a leaf on another folder dispatches set-folder + refetches the host", async () => {
|
|
75
|
+
folderRows = [
|
|
76
|
+
{ id: "f1", name: "A", parentId: null, version: 1 },
|
|
77
|
+
{ id: "f2", name: "B", parentId: null, version: 1 },
|
|
78
|
+
];
|
|
79
|
+
const onReassigned = mock(() => {});
|
|
80
|
+
render(
|
|
81
|
+
<Wrapper>
|
|
82
|
+
<FolderManager filing={filingWith(onReassigned)} />
|
|
83
|
+
</Wrapper>,
|
|
84
|
+
);
|
|
85
|
+
dropLeaf("folder-node-f2", "c-1");
|
|
86
|
+
await waitFor(() =>
|
|
87
|
+
expect(dispatchSpy).toHaveBeenCalledWith(FoldersHandlers.setFolder, {
|
|
88
|
+
folderId: "f2",
|
|
89
|
+
entityType: "credit",
|
|
90
|
+
entityId: "c-1",
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
await waitFor(() => expect(onReassigned).toHaveBeenCalled());
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("dropping a leaf on the unfiled bucket dispatches clear-folder", async () => {
|
|
97
|
+
folderRows = [{ id: "f1", name: "A", parentId: null, version: 1 }];
|
|
98
|
+
render(
|
|
99
|
+
<Wrapper>
|
|
100
|
+
<FolderManager filing={filingWith(() => {})} />
|
|
101
|
+
</Wrapper>,
|
|
102
|
+
);
|
|
103
|
+
dropLeaf("folder-node-unfiled", "c-1");
|
|
104
|
+
await waitFor(() =>
|
|
105
|
+
expect(dispatchSpy).toHaveBeenCalledWith(FoldersHandlers.clearFolder, {
|
|
106
|
+
entityType: "credit",
|
|
107
|
+
entityId: "c-1",
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("dropping a leaf on the folder it already lives in is a no-op (no write)", async () => {
|
|
113
|
+
folderRows = [{ id: "f1", name: "A", parentId: null, version: 1 }];
|
|
114
|
+
render(
|
|
115
|
+
<Wrapper>
|
|
116
|
+
<FolderManager filing={filingWith(() => {})} />
|
|
117
|
+
</Wrapper>,
|
|
118
|
+
);
|
|
119
|
+
dropLeaf("folder-node-f1", "c-1");
|
|
120
|
+
// give any (erroneous) async write a tick to land
|
|
121
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
122
|
+
expect(dispatchSpy).not.toHaveBeenCalled();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("without filing the manager renders no leaves or bucket (backward compatible)", () => {
|
|
126
|
+
folderRows = [{ id: "f1", name: "A", parentId: null, version: 1 }];
|
|
127
|
+
render(
|
|
128
|
+
<Wrapper>
|
|
129
|
+
<FolderManager />
|
|
130
|
+
</Wrapper>,
|
|
131
|
+
);
|
|
132
|
+
expect(screen.getByTestId("folder-node-f1")).toBeTruthy();
|
|
133
|
+
expect(screen.queryByTestId("folder-node-unfiled")).toBeNull();
|
|
134
|
+
expect(screen.queryByTestId("folder-leaf-c-1")).toBeNull();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("delete is confirm-gated; confirming dispatches delete-folder", async () => {
|
|
138
|
+
folderRows = [{ id: "f1", name: "A", parentId: null, version: 1 }];
|
|
139
|
+
render(
|
|
140
|
+
<Wrapper>
|
|
141
|
+
<FolderManager />
|
|
142
|
+
</Wrapper>,
|
|
143
|
+
);
|
|
144
|
+
fireEvent.click(screen.getByTestId("folder-delete-f1"));
|
|
145
|
+
expect(dispatchSpy).not.toHaveBeenCalled(); // no write before confirm
|
|
146
|
+
fireEvent.click(await screen.findByTestId("folder-manager-delete-dialog-confirm"));
|
|
147
|
+
await waitFor(() =>
|
|
148
|
+
expect(dispatchSpy).toHaveBeenCalledWith(FoldersHandlers.deleteFolder, { id: "f1" }),
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("the in-tree new-folder row opens a draft; submitting (Enter) creates a root folder", async () => {
|
|
153
|
+
folderRows = [];
|
|
154
|
+
render(
|
|
155
|
+
<Wrapper>
|
|
156
|
+
<FolderManager />
|
|
157
|
+
</Wrapper>,
|
|
158
|
+
);
|
|
159
|
+
fireEvent.click(screen.getByTestId("folder-manager-new-root"));
|
|
160
|
+
fireEvent.change(document.getElementById("folder-manager-draft") as HTMLInputElement, {
|
|
161
|
+
target: { value: "Inbox" },
|
|
162
|
+
});
|
|
163
|
+
fireEvent.submit(screen.getByTestId("folder-manager-draft"));
|
|
164
|
+
await waitFor(() =>
|
|
165
|
+
expect(dispatchSpy).toHaveBeenCalledWith(FoldersHandlers.createFolder, { name: "Inbox" }),
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
});
|