@stigmer/react 3.11.1-dev.20260812192248 → 3.12.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/activity/useRecentActivity.d.ts +3 -2
- package/activity/useRecentActivity.d.ts.map +1 -1
- package/activity/useRecentActivity.js +3 -2
- package/activity/useRecentActivity.js.map +1 -1
- package/agent/AgentDetailView.d.ts +2 -12
- package/agent/AgentDetailView.d.ts.map +1 -1
- package/agent/AgentDetailView.js +10 -42
- package/agent/AgentDetailView.js.map +1 -1
- package/agent/internal/agentToInput.d.ts.map +1 -1
- package/agent/internal/agentToInput.js +0 -8
- package/agent/internal/agentToInput.js.map +1 -1
- package/dependency-graph/DependencyTreeNode.d.ts.map +1 -1
- package/dependency-graph/DependencyTreeNode.js +1 -7
- package/dependency-graph/DependencyTreeNode.js.map +1 -1
- package/dependency-graph/types.d.ts +8 -20
- package/dependency-graph/types.d.ts.map +1 -1
- package/dependency-graph/useDependencyGraph.d.ts.map +1 -1
- package/dependency-graph/useDependencyGraph.js +2 -20
- package/dependency-graph/useDependencyGraph.js.map +1 -1
- package/index.d.ts +0 -2
- package/index.d.ts.map +1 -1
- package/index.js +0 -6
- package/index.js.map +1 -1
- package/library/useExportResource.d.ts +3 -4
- package/library/useExportResource.d.ts.map +1 -1
- package/library/useExportResource.js +1 -1
- package/library/useExportResource.js.map +1 -1
- package/manifest/EditResourceYamlDialog.d.ts.map +1 -1
- package/manifest/EditResourceYamlDialog.js.map +1 -1
- package/package.json +4 -4
- package/src/activity/useRecentActivity.ts +3 -2
- package/src/agent/AgentDetailView.tsx +6 -130
- package/src/agent/internal/__tests__/agentToInput.test.ts +2 -11
- package/src/agent/internal/agentToInput.ts +0 -11
- package/src/dependency-graph/DependencyTreeNode.tsx +1 -7
- package/src/dependency-graph/__tests__/useDependencyGraph.test.ts +18 -34
- package/src/dependency-graph/types.ts +8 -20
- package/src/dependency-graph/useDependencyGraph.ts +2 -22
- package/src/index.ts +0 -82
- package/src/library/useExportResource.ts +3 -4
- package/src/manifest/EditResourceYamlDialog.tsx +1 -2
- package/src/manifest/__tests__/ApplyManifestDialog.test.tsx +2 -2
- package/src/manifest/__tests__/EditResourceYamlDialog.test.tsx +9 -9
- package/styles.css +1 -1
- package/workflow/CanvasTransitionEdge.d.ts +1 -1
- package/datastore/CollectionRecordsBrowser.d.ts +0 -34
- package/datastore/CollectionRecordsBrowser.d.ts.map +0 -1
- package/datastore/CollectionRecordsBrowser.js +0 -226
- package/datastore/CollectionRecordsBrowser.js.map +0 -1
- package/datastore/CollectionSchemaView.d.ts +0 -29
- package/datastore/CollectionSchemaView.d.ts.map +0 -1
- package/datastore/CollectionSchemaView.js +0 -93
- package/datastore/CollectionSchemaView.js.map +0 -1
- package/datastore/DatastoreDetailView.d.ts +0 -44
- package/datastore/DatastoreDetailView.d.ts.map +0 -1
- package/datastore/DatastoreDetailView.js +0 -76
- package/datastore/DatastoreDetailView.js.map +0 -1
- package/datastore/DatastoreSyncReport.d.ts +0 -19
- package/datastore/DatastoreSyncReport.d.ts.map +0 -1
- package/datastore/DatastoreSyncReport.js +0 -45
- package/datastore/DatastoreSyncReport.js.map +0 -1
- package/datastore/DeleteDatastoreDialog.d.ts +0 -35
- package/datastore/DeleteDatastoreDialog.d.ts.map +0 -1
- package/datastore/DeleteDatastoreDialog.js +0 -90
- package/datastore/DeleteDatastoreDialog.js.map +0 -1
- package/datastore/FieldValueControl.d.ts +0 -55
- package/datastore/FieldValueControl.d.ts.map +0 -1
- package/datastore/FieldValueControl.js +0 -138
- package/datastore/FieldValueControl.js.map +0 -1
- package/datastore/RecordFilterBuilder.d.ts +0 -37
- package/datastore/RecordFilterBuilder.d.ts.map +0 -1
- package/datastore/RecordFilterBuilder.js +0 -122
- package/datastore/RecordFilterBuilder.js.map +0 -1
- package/datastore/RecordFormPanel.d.ts +0 -45
- package/datastore/RecordFormPanel.d.ts.map +0 -1
- package/datastore/RecordFormPanel.js +0 -215
- package/datastore/RecordFormPanel.js.map +0 -1
- package/datastore/index.d.ts +0 -43
- package/datastore/index.d.ts.map +0 -1
- package/datastore/index.js +0 -22
- package/datastore/index.js.map +0 -1
- package/datastore/recordFilter.d.ts +0 -62
- package/datastore/recordFilter.d.ts.map +0 -1
- package/datastore/recordFilter.js +0 -104
- package/datastore/recordFilter.js.map +0 -1
- package/datastore/recordValues.d.ts +0 -85
- package/datastore/recordValues.d.ts.map +0 -1
- package/datastore/recordValues.js +0 -282
- package/datastore/recordValues.js.map +0 -1
- package/datastore/useDatastore.d.ts +0 -39
- package/datastore/useDatastore.d.ts.map +0 -1
- package/datastore/useDatastore.js +0 -44
- package/datastore/useDatastore.js.map +0 -1
- package/datastore/useDatastoreCount.d.ts +0 -50
- package/datastore/useDatastoreCount.d.ts.map +0 -1
- package/datastore/useDatastoreCount.js +0 -27
- package/datastore/useDatastoreCount.js.map +0 -1
- package/datastore/useDatastoreDescription.d.ts +0 -54
- package/datastore/useDatastoreDescription.d.ts.map +0 -1
- package/datastore/useDatastoreDescription.js +0 -50
- package/datastore/useDatastoreDescription.js.map +0 -1
- package/datastore/useDatastoreList.d.ts +0 -57
- package/datastore/useDatastoreList.d.ts.map +0 -1
- package/datastore/useDatastoreList.js +0 -29
- package/datastore/useDatastoreList.js.map +0 -1
- package/datastore/useDatastoreSearch.d.ts +0 -19
- package/datastore/useDatastoreSearch.d.ts.map +0 -1
- package/datastore/useDatastoreSearch.js +0 -22
- package/datastore/useDatastoreSearch.js.map +0 -1
- package/datastore/useDeleteDatastore.d.ts +0 -53
- package/datastore/useDeleteDatastore.d.ts.map +0 -1
- package/datastore/useDeleteDatastore.js +0 -40
- package/datastore/useDeleteDatastore.js.map +0 -1
- package/datastore/useDeleteRecord.d.ts +0 -34
- package/datastore/useDeleteRecord.d.ts.map +0 -1
- package/datastore/useDeleteRecord.js +0 -43
- package/datastore/useDeleteRecord.js.map +0 -1
- package/datastore/useInsertRecord.d.ts +0 -50
- package/datastore/useInsertRecord.d.ts.map +0 -1
- package/datastore/useInsertRecord.js +0 -55
- package/datastore/useInsertRecord.js.map +0 -1
- package/datastore/useRecordCollection.d.ts +0 -104
- package/datastore/useRecordCollection.d.ts.map +0 -1
- package/datastore/useRecordCollection.js +0 -136
- package/datastore/useRecordCollection.js.map +0 -1
- package/datastore/useRecordList.d.ts +0 -66
- package/datastore/useRecordList.d.ts.map +0 -1
- package/datastore/useRecordList.js +0 -58
- package/datastore/useRecordList.js.map +0 -1
- package/datastore/useUpdateRecord.d.ts +0 -52
- package/datastore/useUpdateRecord.d.ts.map +0 -1
- package/datastore/useUpdateRecord.js +0 -55
- package/datastore/useUpdateRecord.js.map +0 -1
- package/src/agent/__tests__/AgentDetailView.datastores.test.tsx +0 -166
- package/src/datastore/CollectionRecordsBrowser.tsx +0 -571
- package/src/datastore/CollectionSchemaView.tsx +0 -247
- package/src/datastore/DatastoreDetailView.tsx +0 -201
- package/src/datastore/DatastoreSyncReport.tsx +0 -105
- package/src/datastore/DeleteDatastoreDialog.tsx +0 -202
- package/src/datastore/FieldValueControl.tsx +0 -289
- package/src/datastore/RecordFilterBuilder.tsx +0 -422
- package/src/datastore/RecordFormPanel.tsx +0 -432
- package/src/datastore/__tests__/CollectionRecordsBrowser.test.tsx +0 -245
- package/src/datastore/__tests__/DeleteDatastoreDialog.test.tsx +0 -133
- package/src/datastore/__tests__/FieldValueControl.test.tsx +0 -160
- package/src/datastore/__tests__/RecordFilterBuilder.test.tsx +0 -155
- package/src/datastore/__tests__/RecordFormPanel.test.tsx +0 -227
- package/src/datastore/__tests__/recordFilter.test.ts +0 -169
- package/src/datastore/__tests__/recordMutations.test.tsx +0 -195
- package/src/datastore/__tests__/recordValues.test.ts +0 -359
- package/src/datastore/__tests__/schemaAndSyncViews.test.tsx +0 -142
- package/src/datastore/__tests__/useDatastoreDescription.test.tsx +0 -143
- package/src/datastore/__tests__/useRecordCollection.test.tsx +0 -250
- package/src/datastore/__tests__/useRecordList.test.tsx +0 -85
- package/src/datastore/index.ts +0 -111
- package/src/datastore/recordFilter.ts +0 -154
- package/src/datastore/recordValues.ts +0 -327
- package/src/datastore/useDatastore.ts +0 -67
- package/src/datastore/useDatastoreCount.ts +0 -69
- package/src/datastore/useDatastoreDescription.ts +0 -90
- package/src/datastore/useDatastoreList.ts +0 -79
- package/src/datastore/useDatastoreSearch.ts +0 -39
- package/src/datastore/useDeleteDatastore.ts +0 -87
- package/src/datastore/useDeleteRecord.ts +0 -77
- package/src/datastore/useInsertRecord.ts +0 -92
- package/src/datastore/useRecordCollection.ts +0 -256
- package/src/datastore/useRecordList.ts +0 -131
- package/src/datastore/useUpdateRecord.ts +0 -95
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
// Tests for the records-specific filter model — typed 1:1 against
|
|
2
|
-
// RecordConditionOp, structurally unable to express anything
|
|
3
|
-
// findRecords cannot serve (DD-008 invariant 2).
|
|
4
|
-
|
|
5
|
-
import { describe, expect, it } from "vitest";
|
|
6
|
-
import { create } from "@bufbuild/protobuf";
|
|
7
|
-
import {
|
|
8
|
-
CollectionDeclarationSchema,
|
|
9
|
-
FieldType,
|
|
10
|
-
} from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/spec_pb";
|
|
11
|
-
import { RecordConditionOp } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/record_io_pb";
|
|
12
|
-
import {
|
|
13
|
-
buildRecordFilter,
|
|
14
|
-
filterableFields,
|
|
15
|
-
formatConditionChip,
|
|
16
|
-
isConditionComplete,
|
|
17
|
-
} from "../recordFilter.js";
|
|
18
|
-
|
|
19
|
-
const COLLECTION = create(CollectionDeclarationSchema, {
|
|
20
|
-
name: "bookings",
|
|
21
|
-
fields: [
|
|
22
|
-
{ name: "slot_date", type: FieldType.date, required: true },
|
|
23
|
-
{ name: "status", type: FieldType.string, enumValues: ["confirmed", "cancelled"] },
|
|
24
|
-
{ name: "notes", type: FieldType.json },
|
|
25
|
-
],
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
describe("filterableFields", () => {
|
|
29
|
-
it("offers declared filterable fields plus the filterable system fields", () => {
|
|
30
|
-
const names = filterableFields(COLLECTION).map((f) => f.name);
|
|
31
|
-
expect(names).toEqual(["slot_date", "status", "id", "created_at", "updated_at"]);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("excludes json fields — not filterable", () => {
|
|
35
|
-
const names = filterableFields(COLLECTION).map((f) => f.name);
|
|
36
|
-
expect(names).not.toContain("notes");
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("system fields carry the server's operator sets", () => {
|
|
40
|
-
const fields = filterableFields(COLLECTION);
|
|
41
|
-
const id = fields.find((f) => f.name === "id")!;
|
|
42
|
-
expect(id.operators).toEqual([RecordConditionOp.eq, RecordConditionOp.is_in]);
|
|
43
|
-
const createdAt = fields.find((f) => f.name === "created_at")!;
|
|
44
|
-
expect(createdAt.operators).toEqual([
|
|
45
|
-
RecordConditionOp.gt,
|
|
46
|
-
RecordConditionOp.gte,
|
|
47
|
-
RecordConditionOp.lt,
|
|
48
|
-
RecordConditionOp.lte,
|
|
49
|
-
]);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("required fields get no null tests; optional ones do", () => {
|
|
53
|
-
const fields = filterableFields(COLLECTION);
|
|
54
|
-
const slotDate = fields.find((f) => f.name === "slot_date")!;
|
|
55
|
-
expect(slotDate.operators).not.toContain(RecordConditionOp.is_null);
|
|
56
|
-
const status = fields.find((f) => f.name === "status")!;
|
|
57
|
-
expect(status.operators).toContain(RecordConditionOp.is_null);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("narrows to the caller's readable fields; system fields stay offered", () => {
|
|
61
|
-
// A field-restricted read grant: the server refuses conditions on
|
|
62
|
-
// unreadable fields, so the builder must not offer them.
|
|
63
|
-
const names = filterableFields(COLLECTION, ["status"]).map((f) => f.name);
|
|
64
|
-
expect(names).toEqual(["status", "id", "created_at", "updated_at"]);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("empty readable_fields means unrestricted (the proto contract)", () => {
|
|
68
|
-
expect(filterableFields(COLLECTION, []).map((f) => f.name)).toEqual(
|
|
69
|
-
filterableFields(COLLECTION).map((f) => f.name),
|
|
70
|
-
);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe("isConditionComplete", () => {
|
|
75
|
-
it("value-less operators are complete without a value", () => {
|
|
76
|
-
expect(
|
|
77
|
-
isConditionComplete({ field: "status", op: RecordConditionOp.is_null }),
|
|
78
|
-
).toBe(true);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("scalar operators need a value", () => {
|
|
82
|
-
expect(isConditionComplete({ field: "status", op: RecordConditionOp.eq })).toBe(false);
|
|
83
|
-
expect(
|
|
84
|
-
isConditionComplete({ field: "status", op: RecordConditionOp.eq, value: "confirmed" }),
|
|
85
|
-
).toBe(true);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("list operators need at least one value", () => {
|
|
89
|
-
expect(
|
|
90
|
-
isConditionComplete({ field: "status", op: RecordConditionOp.is_in, values: [] }),
|
|
91
|
-
).toBe(false);
|
|
92
|
-
expect(
|
|
93
|
-
isConditionComplete({
|
|
94
|
-
field: "status",
|
|
95
|
-
op: RecordConditionOp.is_in,
|
|
96
|
-
values: ["confirmed"],
|
|
97
|
-
}),
|
|
98
|
-
).toBe(true);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("an unspecified operator is never complete", () => {
|
|
102
|
-
expect(
|
|
103
|
-
isConditionComplete({
|
|
104
|
-
field: "status",
|
|
105
|
-
op: RecordConditionOp.record_condition_op_unspecified,
|
|
106
|
-
value: "x",
|
|
107
|
-
}),
|
|
108
|
-
).toBe(false);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe("buildRecordFilter", () => {
|
|
113
|
-
it("returns undefined for no complete conditions (absent filter, not empty)", () => {
|
|
114
|
-
expect(buildRecordFilter([])).toBeUndefined();
|
|
115
|
-
expect(
|
|
116
|
-
buildRecordFilter([{ field: "status", op: RecordConditionOp.eq }]),
|
|
117
|
-
).toBeUndefined();
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("builds proto conditions with scalar values", () => {
|
|
121
|
-
const filter = buildRecordFilter([
|
|
122
|
-
{ field: "status", op: RecordConditionOp.eq, value: "confirmed" },
|
|
123
|
-
])!;
|
|
124
|
-
expect(filter.conditions).toHaveLength(1);
|
|
125
|
-
expect(filter.conditions[0].field).toBe("status");
|
|
126
|
-
expect(filter.conditions[0].op).toBe(RecordConditionOp.eq);
|
|
127
|
-
expect(filter.conditions[0].value?.kind).toEqual({
|
|
128
|
-
case: "stringValue",
|
|
129
|
-
value: "confirmed",
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("builds list values for is_in", () => {
|
|
134
|
-
const filter = buildRecordFilter([
|
|
135
|
-
{
|
|
136
|
-
field: "status",
|
|
137
|
-
op: RecordConditionOp.is_in,
|
|
138
|
-
values: ["confirmed", "cancelled"],
|
|
139
|
-
},
|
|
140
|
-
])!;
|
|
141
|
-
expect(filter.conditions[0].values).toHaveLength(2);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it("leaves the value unset for null tests", () => {
|
|
145
|
-
const filter = buildRecordFilter([
|
|
146
|
-
{ field: "status", op: RecordConditionOp.not_null },
|
|
147
|
-
])!;
|
|
148
|
-
expect(filter.conditions[0].value).toBeUndefined();
|
|
149
|
-
expect(filter.conditions[0].values).toHaveLength(0);
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
describe("formatConditionChip", () => {
|
|
154
|
-
it("formats scalar, list, and value-less conditions", () => {
|
|
155
|
-
expect(
|
|
156
|
-
formatConditionChip({ field: "status", op: RecordConditionOp.eq, value: "confirmed" }),
|
|
157
|
-
).toBe("status = confirmed");
|
|
158
|
-
expect(
|
|
159
|
-
formatConditionChip({
|
|
160
|
-
field: "status",
|
|
161
|
-
op: RecordConditionOp.not_in,
|
|
162
|
-
values: ["a", "b"],
|
|
163
|
-
}),
|
|
164
|
-
).toBe("status not in [a, b]");
|
|
165
|
-
expect(formatConditionChip({ field: "notes", op: RecordConditionOp.is_null })).toBe(
|
|
166
|
-
"notes is empty",
|
|
167
|
-
);
|
|
168
|
-
});
|
|
169
|
-
});
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
// Tests for the three record mutation hooks and the guarded datastore
|
|
2
|
-
// delete. Mutations are imperative: the promise carries the result, the
|
|
3
|
-
// hook carries isPending/error, and errors preserve the server's
|
|
4
|
-
// relayable message byte-for-byte.
|
|
5
|
-
|
|
6
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
7
|
-
import { renderHook, act } from "@testing-library/react";
|
|
8
|
-
import type { ReactNode } from "react";
|
|
9
|
-
import { create } from "@bufbuild/protobuf";
|
|
10
|
-
import { RecordEnvelopeSchema } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/record_io_pb";
|
|
11
|
-
import { StigmerContext } from "../../context";
|
|
12
|
-
import { useInsertRecord } from "../useInsertRecord";
|
|
13
|
-
import { useUpdateRecord } from "../useUpdateRecord";
|
|
14
|
-
import { useDeleteRecord } from "../useDeleteRecord";
|
|
15
|
-
import { useDeleteDatastore } from "../useDeleteDatastore";
|
|
16
|
-
|
|
17
|
-
function wrapper(client: unknown) {
|
|
18
|
-
return function Wrapper({ children }: { children: ReactNode }) {
|
|
19
|
-
return (
|
|
20
|
-
<StigmerContext.Provider value={client as never}>
|
|
21
|
-
{children}
|
|
22
|
-
</StigmerContext.Provider>
|
|
23
|
-
);
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const SCOPE = { org: "acme", datastore: "clinic", collection: "bookings" } as const;
|
|
28
|
-
|
|
29
|
-
describe("useInsertRecord", () => {
|
|
30
|
-
beforeEach(() => vi.restoreAllMocks());
|
|
31
|
-
|
|
32
|
-
it("inserts and resolves with the server-stamped envelope", async () => {
|
|
33
|
-
const stamped = create(RecordEnvelopeSchema, { id: "dsr_new" });
|
|
34
|
-
const insertRecord = vi.fn().mockResolvedValue(stamped);
|
|
35
|
-
const client = { datastore: { insertRecord } } as never;
|
|
36
|
-
|
|
37
|
-
const { result } = renderHook(() => useInsertRecord(), { wrapper: wrapper(client) });
|
|
38
|
-
|
|
39
|
-
let returned: unknown;
|
|
40
|
-
await act(async () => {
|
|
41
|
-
returned = await result.current.insertRecord({
|
|
42
|
-
...SCOPE,
|
|
43
|
-
record: { slot_date: "2026-07-22", slot_time: "09:30:00" },
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
expect(returned).toBe(stamped);
|
|
48
|
-
expect(insertRecord).toHaveBeenCalledWith(
|
|
49
|
-
expect.objectContaining({
|
|
50
|
-
org: "acme",
|
|
51
|
-
datastore: "clinic",
|
|
52
|
-
collection: "bookings",
|
|
53
|
-
partition: "",
|
|
54
|
-
record: { slot_date: "2026-07-22", slot_time: "09:30:00" },
|
|
55
|
-
}),
|
|
56
|
-
);
|
|
57
|
-
expect(result.current.error).toBeNull();
|
|
58
|
-
expect(result.current.isInserting).toBe(false);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("threads the partition (DD-010)", async () => {
|
|
62
|
-
const insertRecord = vi.fn().mockResolvedValue(create(RecordEnvelopeSchema, {}));
|
|
63
|
-
const client = { datastore: { insertRecord } } as never;
|
|
64
|
-
const { result } = renderHook(() => useInsertRecord(), { wrapper: wrapper(client) });
|
|
65
|
-
|
|
66
|
-
await act(async () => {
|
|
67
|
-
await result.current.insertRecord({ ...SCOPE, partition: "dr-alt", record: {} });
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
expect(insertRecord).toHaveBeenCalledWith(
|
|
71
|
-
expect.objectContaining({ partition: "dr-alt" }),
|
|
72
|
-
);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("preserves the constraint violation's message verbatim and rethrows", async () => {
|
|
76
|
-
const violation = new Error("that slot is already booked");
|
|
77
|
-
const client = {
|
|
78
|
-
datastore: { insertRecord: vi.fn().mockRejectedValue(violation) },
|
|
79
|
-
} as never;
|
|
80
|
-
const { result } = renderHook(() => useInsertRecord(), { wrapper: wrapper(client) });
|
|
81
|
-
|
|
82
|
-
await act(async () => {
|
|
83
|
-
await expect(
|
|
84
|
-
result.current.insertRecord({ ...SCOPE, record: {} }),
|
|
85
|
-
).rejects.toThrow("that slot is already booked");
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
expect(result.current.error?.message).toBe("that slot is already booked");
|
|
89
|
-
|
|
90
|
-
act(() => result.current.clearError());
|
|
91
|
-
expect(result.current.error).toBeNull();
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe("useUpdateRecord", () => {
|
|
96
|
-
beforeEach(() => vi.restoreAllMocks());
|
|
97
|
-
|
|
98
|
-
it("submits the partial-merge payload including explicit nulls", async () => {
|
|
99
|
-
const updateRecord = vi.fn().mockResolvedValue(create(RecordEnvelopeSchema, {}));
|
|
100
|
-
const client = { datastore: { updateRecord } } as never;
|
|
101
|
-
const { result } = renderHook(() => useUpdateRecord(), { wrapper: wrapper(client) });
|
|
102
|
-
|
|
103
|
-
await act(async () => {
|
|
104
|
-
await result.current.updateRecord({
|
|
105
|
-
...SCOPE,
|
|
106
|
-
id: "dsr_1",
|
|
107
|
-
fields: { patient_phone: "+15550100", notes: null },
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
expect(updateRecord).toHaveBeenCalledWith(
|
|
112
|
-
expect.objectContaining({
|
|
113
|
-
id: "dsr_1",
|
|
114
|
-
// Tri-state: dirty value + explicit clear travel; untouched
|
|
115
|
-
// fields are absent from the payload entirely.
|
|
116
|
-
fields: { patient_phone: "+15550100", notes: null },
|
|
117
|
-
}),
|
|
118
|
-
);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it("surfaces update denials verbatim", async () => {
|
|
122
|
-
const denial = new Error("you are not allowed to update records in bookings");
|
|
123
|
-
const client = {
|
|
124
|
-
datastore: { updateRecord: vi.fn().mockRejectedValue(denial) },
|
|
125
|
-
} as never;
|
|
126
|
-
const { result } = renderHook(() => useUpdateRecord(), { wrapper: wrapper(client) });
|
|
127
|
-
|
|
128
|
-
await act(async () => {
|
|
129
|
-
await expect(
|
|
130
|
-
result.current.updateRecord({ ...SCOPE, id: "dsr_1", fields: {} }),
|
|
131
|
-
).rejects.toThrow();
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
expect(result.current.error?.message).toBe(
|
|
135
|
-
"you are not allowed to update records in bookings",
|
|
136
|
-
);
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe("useDeleteRecord", () => {
|
|
141
|
-
beforeEach(() => vi.restoreAllMocks());
|
|
142
|
-
|
|
143
|
-
it("deletes by id and resolves with the final envelope", async () => {
|
|
144
|
-
const last = create(RecordEnvelopeSchema, { id: "dsr_1" });
|
|
145
|
-
const deleteRecord = vi.fn().mockResolvedValue(last);
|
|
146
|
-
const client = { datastore: { deleteRecord } } as never;
|
|
147
|
-
const { result } = renderHook(() => useDeleteRecord(), { wrapper: wrapper(client) });
|
|
148
|
-
|
|
149
|
-
let returned: unknown;
|
|
150
|
-
await act(async () => {
|
|
151
|
-
returned = await result.current.deleteRecord({ ...SCOPE, id: "dsr_1" });
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
expect(returned).toBe(last);
|
|
155
|
-
expect(deleteRecord).toHaveBeenCalledWith(
|
|
156
|
-
expect.objectContaining({ id: "dsr_1", collection: "bookings" }),
|
|
157
|
-
);
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
describe("useDeleteDatastore", () => {
|
|
162
|
-
beforeEach(() => vi.restoreAllMocks());
|
|
163
|
-
|
|
164
|
-
it("passes the force acknowledgment through", async () => {
|
|
165
|
-
const deleted = { metadata: { id: "dst_1" } };
|
|
166
|
-
const del = vi.fn().mockResolvedValue(deleted);
|
|
167
|
-
const client = { datastore: { delete: del } } as never;
|
|
168
|
-
const { result } = renderHook(() => useDeleteDatastore(), { wrapper: wrapper(client) });
|
|
169
|
-
|
|
170
|
-
await act(async () => {
|
|
171
|
-
await result.current.deleteDatastore({ resourceId: "dst_1", force: true });
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
expect(del).toHaveBeenCalledWith({ resourceId: "dst_1", force: true });
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("surfaces guard refusals verbatim — the server stays authoritative", async () => {
|
|
178
|
-
const guard = new Error(
|
|
179
|
-
'datastore "clinic" holds 214 records across 2 collections; pass force to acknowledge their destruction',
|
|
180
|
-
);
|
|
181
|
-
const client = {
|
|
182
|
-
datastore: { delete: vi.fn().mockRejectedValue(guard) },
|
|
183
|
-
} as never;
|
|
184
|
-
const { result } = renderHook(() => useDeleteDatastore(), { wrapper: wrapper(client) });
|
|
185
|
-
|
|
186
|
-
await act(async () => {
|
|
187
|
-
await expect(
|
|
188
|
-
result.current.deleteDatastore({ resourceId: "dst_1" }),
|
|
189
|
-
).rejects.toThrow();
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
expect(result.current.error?.message).toBe(guard.message);
|
|
193
|
-
expect(result.current.isDeleting).toBe(false);
|
|
194
|
-
});
|
|
195
|
-
});
|