sh3-core 0.20.1 → 0.20.3
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/dist/BrandSlot.svelte +2 -2
- package/dist/actions/ctx-actions.svelte.test.js +2 -2
- package/dist/artifact.d.ts +2 -0
- package/dist/boot/satellitePayload.d.ts +2 -0
- package/dist/boot/satellitePayload.test.js +19 -0
- package/dist/build.d.ts +7 -1
- package/dist/build.js +22 -3
- package/dist/build.test.js +27 -1
- package/dist/createShell.js +34 -9
- package/dist/documents/backends.d.ts +12 -0
- package/dist/documents/backends.js +230 -3
- package/dist/documents/backends.test.js +147 -1
- package/dist/documents/browse.d.ts +20 -0
- package/dist/documents/browse.js +35 -0
- package/dist/documents/browse.test.js +125 -0
- package/dist/documents/config.d.ts +2 -4
- package/dist/documents/config.js +3 -7
- package/dist/documents/handle.js +40 -0
- package/dist/documents/handle.test.js +88 -1
- package/dist/documents/http-backend.d.ts +11 -0
- package/dist/documents/http-backend.js +86 -0
- package/dist/documents/http-backend.test.js +117 -1
- package/dist/documents/index.d.ts +1 -1
- package/dist/documents/index.js +1 -1
- package/dist/documents/picker-api.test.js +2 -2
- package/dist/documents/types.d.ts +87 -14
- package/dist/documents/types.js +4 -0
- package/dist/host-entry.d.ts +1 -1
- package/dist/host-entry.js +1 -1
- package/dist/host.d.ts +1 -1
- package/dist/host.js +1 -1
- package/dist/layout/slotHostPool.svelte.js +2 -2
- package/dist/overlays/FloatFrame.svelte +1 -0
- package/dist/primitives/widgets/DocumentFilePicker.d.ts +6 -2
- package/dist/primitives/widgets/DocumentFilePicker.js +12 -5
- package/dist/primitives/widgets/DocumentFilePicker.svelte +23 -1
- package/dist/primitives/widgets/DocumentFilePicker.svelte.d.ts +14 -0
- package/dist/primitives/widgets/DocumentFilePicker.test.d.ts +1 -0
- package/dist/primitives/widgets/DocumentFilePicker.test.js +33 -0
- package/dist/primitives/widgets/DocumentOpener.svelte +20 -0
- package/dist/primitives/widgets/DocumentOpener.svelte.d.ts +14 -0
- package/dist/primitives/widgets/DocumentSaver.svelte +17 -0
- package/dist/primitives/widgets/DocumentSaver.svelte.d.ts +13 -0
- package/dist/primitives/widgets/_DocumentBrowser.svelte +414 -27
- package/dist/primitives/widgets/_DocumentBrowser.svelte.d.ts +12 -0
- package/dist/primitives/widgets/_DocumentBrowser.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/_DocumentBrowser.svelte.test.js +277 -0
- package/dist/primitives/widgets/_FolderConfirmDelete.svelte +57 -0
- package/dist/primitives/widgets/_FolderConfirmDelete.svelte.d.ts +12 -0
- package/dist/projects/session-state.svelte.d.ts +3 -0
- package/dist/projects/session-state.svelte.js +25 -0
- package/dist/projects/session-state.test.js +43 -2
- package/dist/projects-shard/ProjectsSection.svelte +14 -18
- package/dist/runtime/runVerb-shell.test.js +2 -2
- package/dist/runtime/runVerb.test.js +2 -2
- package/dist/sh3Api/headless.js +10 -0
- package/dist/sh3core-shard/appActions.js +5 -2
- package/dist/shards/activate-browse.test.js +2 -2
- package/dist/shards/activate-contributions.test.js +2 -2
- package/dist/shards/activate-error-isolation.test.js +3 -3
- package/dist/shards/activate-on-key-revoked.test.js +2 -2
- package/dist/shards/activate-runtime.test.js +2 -2
- package/dist/shards/activate.svelte.js +5 -5
- package/dist/shards/ctx-fetch.test.js +4 -4
- package/dist/shell-shard/Terminal.svelte +4 -1
- package/dist/shell-shard/Terminal.svelte.d.ts +2 -0
- package/dist/shell-shard/dispatch.d.ts +2 -0
- package/dist/shell-shard/dispatch.js +2 -0
- package/dist/shell-shard/manifest.js +7 -1
- package/dist/shell-shard/shellShard.svelte.js +1 -1
- package/dist/shell-shard/verbs/cat.d.ts +2 -0
- package/dist/shell-shard/verbs/cat.js +35 -0
- package/dist/shell-shard/verbs/cat.test.d.ts +1 -0
- package/dist/shell-shard/verbs/cat.test.js +49 -0
- package/dist/shell-shard/verbs/index.js +12 -0
- package/dist/shell-shard/verbs/ls.d.ts +2 -0
- package/dist/shell-shard/verbs/ls.js +48 -0
- package/dist/shell-shard/verbs/ls.test.d.ts +1 -0
- package/dist/shell-shard/verbs/ls.test.js +64 -0
- package/dist/shell-shard/verbs/mkdir.d.ts +2 -0
- package/dist/shell-shard/verbs/mkdir.js +30 -0
- package/dist/shell-shard/verbs/mkdir.test.d.ts +1 -0
- package/dist/shell-shard/verbs/mkdir.test.js +48 -0
- package/dist/shell-shard/verbs/mv.d.ts +2 -0
- package/dist/shell-shard/verbs/mv.js +33 -0
- package/dist/shell-shard/verbs/mv.test.d.ts +1 -0
- package/dist/shell-shard/verbs/mv.test.js +55 -0
- package/dist/shell-shard/verbs/rm.d.ts +2 -0
- package/dist/shell-shard/verbs/rm.js +28 -0
- package/dist/shell-shard/verbs/rm.test.d.ts +1 -0
- package/dist/shell-shard/verbs/rm.test.js +47 -0
- package/dist/shell-shard/verbs/scope-parse.d.ts +7 -0
- package/dist/shell-shard/verbs/scope-parse.js +33 -0
- package/dist/shell-shard/verbs/scope-parse.test.d.ts +1 -0
- package/dist/shell-shard/verbs/scope-parse.test.js +76 -0
- package/dist/shell-shard/verbs/xfer.d.ts +2 -0
- package/dist/shell-shard/verbs/xfer.js +87 -0
- package/dist/shell-shard/verbs/xfer.test.d.ts +1 -0
- package/dist/shell-shard/verbs/xfer.test.js +107 -0
- package/dist/verbs/types.d.ts +18 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { render, fireEvent, screen } from '@testing-library/svelte';
|
|
3
|
+
import DocumentBrowser from './_DocumentBrowser.svelte';
|
|
4
|
+
function makeHandle() {
|
|
5
|
+
return {
|
|
6
|
+
mkdir: vi.fn().mockResolvedValue(undefined),
|
|
7
|
+
rmdir: vi.fn().mockResolvedValue(undefined),
|
|
8
|
+
renameFolder: vi.fn().mockResolvedValue(undefined),
|
|
9
|
+
rename: vi.fn().mockResolvedValue(undefined),
|
|
10
|
+
delete: vi.fn().mockResolvedValue(undefined),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const sampleDocs = [
|
|
14
|
+
{ shardId: 'sh1', path: 'a.md', size: 100, lastModified: 0 },
|
|
15
|
+
{ shardId: 'sh1', path: 'sub/b.md', size: 200, lastModified: 0 },
|
|
16
|
+
];
|
|
17
|
+
function baseProps(extra = {}) {
|
|
18
|
+
return Object.assign({ mode: 'open', docs: sampleDocs, onCommit: vi.fn(), onCancel: vi.fn(), close: vi.fn() }, extra);
|
|
19
|
+
}
|
|
20
|
+
describe('_DocumentBrowser toolbar visibility', () => {
|
|
21
|
+
it('toolbar is hidden at the shard-list view (shardId === null)', async () => {
|
|
22
|
+
render(DocumentBrowser, { props: baseProps({ handle: makeHandle() }) });
|
|
23
|
+
expect(document.querySelector('.sh3-doc-browser__toolbar')).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
it('toolbar is visible when inside a shard and handle is provided', async () => {
|
|
26
|
+
const { container } = render(DocumentBrowser, { props: baseProps({ handle: makeHandle() }) });
|
|
27
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
28
|
+
expect(container.querySelector('.sh3-doc-browser__toolbar')).not.toBeNull();
|
|
29
|
+
});
|
|
30
|
+
it('toolbar is hidden when readOnlyShard returns true', async () => {
|
|
31
|
+
const { container } = render(DocumentBrowser, {
|
|
32
|
+
props: baseProps({ handle: makeHandle(), readOnlyShard: () => true }),
|
|
33
|
+
});
|
|
34
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
35
|
+
expect(container.querySelector('.sh3-doc-browser__toolbar')).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
it('toolbar is hidden when no handle is provided', async () => {
|
|
38
|
+
const { container } = render(DocumentBrowser, { props: baseProps() });
|
|
39
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
40
|
+
expect(container.querySelector('.sh3-doc-browser__toolbar')).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('_DocumentBrowser navigation', () => {
|
|
44
|
+
it('clicking a shard navigates into it and shows its contents', async () => {
|
|
45
|
+
render(DocumentBrowser, { props: baseProps() });
|
|
46
|
+
expect(screen.getByText('sh1')).not.toBeNull();
|
|
47
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
48
|
+
expect(screen.getByText('a.md')).not.toBeNull();
|
|
49
|
+
expect(screen.getByText('sub')).not.toBeNull();
|
|
50
|
+
});
|
|
51
|
+
it('double-clicking a file commits it in open mode', async () => {
|
|
52
|
+
const onCommit = vi.fn();
|
|
53
|
+
const close = vi.fn();
|
|
54
|
+
render(DocumentBrowser, { props: baseProps({ onCommit, close }) });
|
|
55
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
56
|
+
await fireEvent.dblClick(screen.getByText('a.md'));
|
|
57
|
+
expect(onCommit).toHaveBeenCalledWith({ shardId: 'sh1', path: 'a.md', kind: 'file' });
|
|
58
|
+
expect(close).toHaveBeenCalled();
|
|
59
|
+
});
|
|
60
|
+
it('double-clicking a folder navigates into it', async () => {
|
|
61
|
+
render(DocumentBrowser, { props: baseProps() });
|
|
62
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
63
|
+
await fireEvent.dblClick(screen.getByText('sub'));
|
|
64
|
+
expect(screen.getByText('b.md')).not.toBeNull();
|
|
65
|
+
});
|
|
66
|
+
it('clicking a folder with selectable=folder selects it, not navigates', async () => {
|
|
67
|
+
const onCommit = vi.fn();
|
|
68
|
+
render(DocumentBrowser, {
|
|
69
|
+
props: baseProps({ onCommit, selectable: 'folder' }),
|
|
70
|
+
});
|
|
71
|
+
// Use dblClick to navigate into sh1 (dblClick always navigates)
|
|
72
|
+
await fireEvent.dblClick(screen.getByText('sh1'));
|
|
73
|
+
// Now inside sh1: clicking sub should select it, not navigate
|
|
74
|
+
await fireEvent.click(screen.getByText('sub'));
|
|
75
|
+
expect(screen.queryByText('b.md')).toBeNull(); // not navigated
|
|
76
|
+
// Open button should be enabled since a folder is selected
|
|
77
|
+
const openBtn = screen.getByText('Open');
|
|
78
|
+
expect(openBtn.disabled).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe('_DocumentBrowser commit and cancel', () => {
|
|
82
|
+
it('Open button is disabled when nothing is selected', async () => {
|
|
83
|
+
render(DocumentBrowser, { props: baseProps() });
|
|
84
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
85
|
+
const openBtn = screen.getByText('Open');
|
|
86
|
+
expect(openBtn.disabled).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
it('Open button is enabled when a file is selected', async () => {
|
|
89
|
+
render(DocumentBrowser, { props: baseProps() });
|
|
90
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
91
|
+
await fireEvent.click(screen.getByText('a.md'));
|
|
92
|
+
const openBtn = screen.getByText('Open');
|
|
93
|
+
expect(openBtn.disabled).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
it('clicking Open commits the selected file', async () => {
|
|
96
|
+
const onCommit = vi.fn();
|
|
97
|
+
const close = vi.fn();
|
|
98
|
+
render(DocumentBrowser, { props: baseProps({ onCommit, close }) });
|
|
99
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
100
|
+
await fireEvent.click(screen.getByText('a.md'));
|
|
101
|
+
await fireEvent.click(screen.getByText('Open'));
|
|
102
|
+
expect(onCommit).toHaveBeenCalledWith({ shardId: 'sh1', path: 'a.md', kind: 'file' });
|
|
103
|
+
expect(close).toHaveBeenCalled();
|
|
104
|
+
});
|
|
105
|
+
it('Cancel calls onCancel and close', async () => {
|
|
106
|
+
const onCancel = vi.fn();
|
|
107
|
+
const close = vi.fn();
|
|
108
|
+
render(DocumentBrowser, { props: baseProps({ onCancel, close }) });
|
|
109
|
+
await fireEvent.click(screen.getByText('Cancel'));
|
|
110
|
+
expect(onCancel).toHaveBeenCalled();
|
|
111
|
+
expect(close).toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('_DocumentBrowser new folder', () => {
|
|
115
|
+
beforeEach(() => { vi.clearAllMocks(); });
|
|
116
|
+
it('clicking New folder shows an inline editable row', async () => {
|
|
117
|
+
const handle = makeHandle();
|
|
118
|
+
const { container } = render(DocumentBrowser, { props: baseProps({ handle }) });
|
|
119
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
120
|
+
const newFolderBtn = container.querySelector('[title="New folder"]');
|
|
121
|
+
await fireEvent.click(newFolderBtn);
|
|
122
|
+
const input = container.querySelector('.sh3-doc-browser__rename-input');
|
|
123
|
+
expect(input).not.toBeNull();
|
|
124
|
+
});
|
|
125
|
+
it('entering a name and pressing Enter calls handle.mkdir', async () => {
|
|
126
|
+
const handle = makeHandle();
|
|
127
|
+
const { container } = render(DocumentBrowser, { props: baseProps({ handle }) });
|
|
128
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
129
|
+
const newFolderBtn = container.querySelector('[title="New folder"]');
|
|
130
|
+
await fireEvent.click(newFolderBtn);
|
|
131
|
+
const input = container.querySelector('.sh3-doc-browser__rename-input');
|
|
132
|
+
await fireEvent.input(input, { target: { value: 'newdir' } });
|
|
133
|
+
await fireEvent.keyDown(input, { key: 'Enter' });
|
|
134
|
+
expect(handle.mkdir).toHaveBeenCalledWith('sh1', 'newdir');
|
|
135
|
+
});
|
|
136
|
+
it('Esc cancels new folder without calling mkdir', async () => {
|
|
137
|
+
const handle = makeHandle();
|
|
138
|
+
const { container } = render(DocumentBrowser, { props: baseProps({ handle }) });
|
|
139
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
140
|
+
const newFolderBtn = container.querySelector('[title="New folder"]');
|
|
141
|
+
await fireEvent.click(newFolderBtn);
|
|
142
|
+
const input = container.querySelector('.sh3-doc-browser__rename-input');
|
|
143
|
+
await fireEvent.keyDown(input, { key: 'Escape' });
|
|
144
|
+
expect(handle.mkdir).not.toHaveBeenCalled();
|
|
145
|
+
expect(container.querySelector('.sh3-doc-browser__rename-input')).toBeNull();
|
|
146
|
+
});
|
|
147
|
+
it('duplicate name shows error and does not call mkdir', async () => {
|
|
148
|
+
const handle = makeHandle();
|
|
149
|
+
const { container } = render(DocumentBrowser, { props: baseProps({ handle }) });
|
|
150
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
151
|
+
const newFolderBtn = container.querySelector('[title="New folder"]');
|
|
152
|
+
await fireEvent.click(newFolderBtn);
|
|
153
|
+
const input = container.querySelector('.sh3-doc-browser__rename-input');
|
|
154
|
+
// 'sub' already exists as a folder from sampleDocs
|
|
155
|
+
await fireEvent.input(input, { target: { value: 'sub' } });
|
|
156
|
+
await fireEvent.keyDown(input, { key: 'Enter' });
|
|
157
|
+
expect(handle.mkdir).not.toHaveBeenCalled();
|
|
158
|
+
const errorEl = container.querySelector('.sh3-doc-browser__toolbar-error');
|
|
159
|
+
expect(errorEl === null || errorEl === void 0 ? void 0 : errorEl.textContent).toMatch(/already exists/);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
describe('_DocumentBrowser rename', () => {
|
|
163
|
+
beforeEach(() => { vi.clearAllMocks(); });
|
|
164
|
+
it('clicking Rename while a file is selected shows inline input', async () => {
|
|
165
|
+
const handle = makeHandle();
|
|
166
|
+
const { container } = render(DocumentBrowser, { props: baseProps({ handle }) });
|
|
167
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
168
|
+
await fireEvent.click(screen.getByText('a.md'));
|
|
169
|
+
const renameBtn = container.querySelector('[title="Rename"]');
|
|
170
|
+
await fireEvent.click(renameBtn);
|
|
171
|
+
const input = container.querySelector('.sh3-doc-browser__rename-input');
|
|
172
|
+
expect(input).not.toBeNull();
|
|
173
|
+
expect(input.value).toBe('a.md');
|
|
174
|
+
});
|
|
175
|
+
it('confirming rename calls handle.rename', async () => {
|
|
176
|
+
const handle = makeHandle();
|
|
177
|
+
const { container } = render(DocumentBrowser, { props: baseProps({ handle }) });
|
|
178
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
179
|
+
await fireEvent.click(screen.getByText('a.md'));
|
|
180
|
+
const renameBtn = container.querySelector('[title="Rename"]');
|
|
181
|
+
await fireEvent.click(renameBtn);
|
|
182
|
+
const input = container.querySelector('.sh3-doc-browser__rename-input');
|
|
183
|
+
await fireEvent.input(input, { target: { value: 'renamed.md' } });
|
|
184
|
+
await fireEvent.keyDown(input, { key: 'Enter' });
|
|
185
|
+
expect(handle.rename).toHaveBeenCalledWith('sh1', 'a.md', 'renamed.md');
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
describe('_DocumentBrowser delete', () => {
|
|
189
|
+
beforeEach(() => { vi.clearAllMocks(); });
|
|
190
|
+
it('clicking Delete while a file is selected shows confirm overlay', async () => {
|
|
191
|
+
const handle = makeHandle();
|
|
192
|
+
const { container } = render(DocumentBrowser, { props: baseProps({ handle }) });
|
|
193
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
194
|
+
await fireEvent.click(screen.getByText('a.md'));
|
|
195
|
+
const deleteBtn = container.querySelector('[title="Delete"]');
|
|
196
|
+
await fireEvent.click(deleteBtn);
|
|
197
|
+
expect(container.querySelector('.sh3-confirm-delete')).not.toBeNull();
|
|
198
|
+
});
|
|
199
|
+
it('confirming delete calls handle.delete', async () => {
|
|
200
|
+
const handle = makeHandle();
|
|
201
|
+
const { container } = render(DocumentBrowser, { props: baseProps({ handle }) });
|
|
202
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
203
|
+
await fireEvent.click(screen.getByText('a.md'));
|
|
204
|
+
const deleteBtn = container.querySelector('[title="Delete"]');
|
|
205
|
+
await fireEvent.click(deleteBtn);
|
|
206
|
+
const confirmBtn = container.querySelector('.sh3-confirm-delete__btn--danger');
|
|
207
|
+
await fireEvent.click(confirmBtn);
|
|
208
|
+
expect(handle.delete).toHaveBeenCalledWith('sh1', 'a.md');
|
|
209
|
+
});
|
|
210
|
+
it('cancelling delete hides the overlay without calling delete', async () => {
|
|
211
|
+
const handle = makeHandle();
|
|
212
|
+
const { container } = render(DocumentBrowser, { props: baseProps({ handle }) });
|
|
213
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
214
|
+
await fireEvent.click(screen.getByText('a.md'));
|
|
215
|
+
const deleteBtn = container.querySelector('[title="Delete"]');
|
|
216
|
+
await fireEvent.click(deleteBtn);
|
|
217
|
+
const cancelBtn = container.querySelector('.sh3-confirm-delete__btn:not(.sh3-confirm-delete__btn--danger)');
|
|
218
|
+
await fireEvent.click(cancelBtn);
|
|
219
|
+
expect(handle.delete).not.toHaveBeenCalled();
|
|
220
|
+
expect(container.querySelector('.sh3-confirm-delete')).toBeNull();
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
describe('_DocumentBrowser cut and paste', () => {
|
|
224
|
+
beforeEach(() => { vi.clearAllMocks(); });
|
|
225
|
+
it('Paste button is disabled when clipboard is empty', async () => {
|
|
226
|
+
const handle = makeHandle();
|
|
227
|
+
const { container } = render(DocumentBrowser, { props: baseProps({ handle }) });
|
|
228
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
229
|
+
const pasteBtn = container.querySelector('[title="Paste"]');
|
|
230
|
+
expect(pasteBtn.disabled).toBe(true);
|
|
231
|
+
});
|
|
232
|
+
it('Cut then Paste calls handle.rename with new path', async () => {
|
|
233
|
+
const handle = makeHandle();
|
|
234
|
+
const { container } = render(DocumentBrowser, { props: baseProps({ handle }) });
|
|
235
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
236
|
+
await fireEvent.click(screen.getByText('a.md'));
|
|
237
|
+
const cutBtn = container.querySelector('[title="Cut"]');
|
|
238
|
+
await fireEvent.click(cutBtn);
|
|
239
|
+
// Navigate into sub folder (dblclick)
|
|
240
|
+
await fireEvent.dblClick(screen.getByText('sub'));
|
|
241
|
+
const pasteBtn = container.querySelector('[title="Paste"]');
|
|
242
|
+
expect(pasteBtn.disabled).toBe(false);
|
|
243
|
+
await fireEvent.click(pasteBtn);
|
|
244
|
+
expect(handle.rename).toHaveBeenCalledWith('sh1', 'a.md', 'sub/a.md');
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
describe('_DocumentBrowser save mode', () => {
|
|
248
|
+
it('shows filename input when inside a shard in save mode', async () => {
|
|
249
|
+
const { container } = render(DocumentBrowser, {
|
|
250
|
+
props: Object.assign(Object.assign({}, baseProps()), { mode: 'save' }),
|
|
251
|
+
});
|
|
252
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
253
|
+
expect(container.querySelector('.sh3-doc-browser__save-input')).not.toBeNull();
|
|
254
|
+
});
|
|
255
|
+
it('clicking a file in save mode populates the filename field', async () => {
|
|
256
|
+
const { container } = render(DocumentBrowser, {
|
|
257
|
+
props: Object.assign(Object.assign({}, baseProps()), { mode: 'save' }),
|
|
258
|
+
});
|
|
259
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
260
|
+
await fireEvent.click(screen.getByText('a.md'));
|
|
261
|
+
const input = container.querySelector('.sh3-doc-browser__save-input');
|
|
262
|
+
expect(input.value).toBe('a.md');
|
|
263
|
+
});
|
|
264
|
+
it('Save commits the typed path', async () => {
|
|
265
|
+
const onCommit = vi.fn();
|
|
266
|
+
const close = vi.fn();
|
|
267
|
+
const { container } = render(DocumentBrowser, {
|
|
268
|
+
props: Object.assign(Object.assign({}, baseProps({ onCommit, close })), { mode: 'save' }),
|
|
269
|
+
});
|
|
270
|
+
await fireEvent.click(screen.getByText('sh1'));
|
|
271
|
+
const input = container.querySelector('.sh3-doc-browser__save-input');
|
|
272
|
+
await fireEvent.input(input, { target: { value: 'report.txt' } });
|
|
273
|
+
await fireEvent.click(screen.getByText('Save'));
|
|
274
|
+
expect(onCommit).toHaveBeenCalledWith('sh1/report.txt');
|
|
275
|
+
expect(close).toHaveBeenCalled();
|
|
276
|
+
});
|
|
277
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {
|
|
3
|
+
item,
|
|
4
|
+
childCount,
|
|
5
|
+
onConfirm,
|
|
6
|
+
onCancel,
|
|
7
|
+
}: {
|
|
8
|
+
item: { kind: 'file' | 'folder'; name: string };
|
|
9
|
+
childCount: number;
|
|
10
|
+
onConfirm: () => void;
|
|
11
|
+
onCancel: () => void;
|
|
12
|
+
} = $props();
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<div class="sh3-confirm-delete">
|
|
16
|
+
<div class="sh3-confirm-delete__title">
|
|
17
|
+
{#if item.kind === 'file'}
|
|
18
|
+
Delete "{item.name}"?
|
|
19
|
+
{:else if childCount === 0}
|
|
20
|
+
Delete folder "{item.name}"?
|
|
21
|
+
{:else}
|
|
22
|
+
Delete folder "{item.name}" and {childCount} item{childCount === 1 ? '' : 's'}?
|
|
23
|
+
{/if}
|
|
24
|
+
</div>
|
|
25
|
+
<div class="sh3-confirm-delete__buttons">
|
|
26
|
+
<button type="button" class="sh3-confirm-delete__btn" onclick={onCancel}>Cancel</button>
|
|
27
|
+
<button type="button" class="sh3-confirm-delete__btn sh3-confirm-delete__btn--danger" onclick={onConfirm}>Delete</button>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<style>
|
|
32
|
+
.sh3-confirm-delete {
|
|
33
|
+
padding: 12px 16px;
|
|
34
|
+
min-width: 240px;
|
|
35
|
+
color: var(--sh3-fg);
|
|
36
|
+
font-size: 0.8125rem;
|
|
37
|
+
}
|
|
38
|
+
.sh3-confirm-delete__title { font-size: 0.875rem; margin-bottom: 12px; }
|
|
39
|
+
.sh3-confirm-delete__buttons { display: flex; justify-content: flex-end; gap: 8px; }
|
|
40
|
+
.sh3-confirm-delete__btn {
|
|
41
|
+
display: inline-flex; align-items: center;
|
|
42
|
+
height: 26px; padding: 0 12px;
|
|
43
|
+
border: 1px solid var(--sh3-border);
|
|
44
|
+
border-radius: var(--sh3-radius-sm);
|
|
45
|
+
background: var(--sh3-bg-elevated);
|
|
46
|
+
color: var(--sh3-fg);
|
|
47
|
+
font: inherit; font-size: 0.75rem;
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
}
|
|
50
|
+
.sh3-confirm-delete__btn:hover { background: var(--sh3-bg); }
|
|
51
|
+
.sh3-confirm-delete__btn--danger {
|
|
52
|
+
background: var(--sh3-error);
|
|
53
|
+
color: var(--sh3-fg-on-error);
|
|
54
|
+
border-color: var(--sh3-error);
|
|
55
|
+
}
|
|
56
|
+
.sh3-confirm-delete__btn--danger:hover { filter: brightness(1.1); }
|
|
57
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
item: {
|
|
3
|
+
kind: 'file' | 'folder';
|
|
4
|
+
name: string;
|
|
5
|
+
};
|
|
6
|
+
childCount: number;
|
|
7
|
+
onConfirm: () => void;
|
|
8
|
+
onCancel: () => void;
|
|
9
|
+
};
|
|
10
|
+
declare const FolderConfirmDelete: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
11
|
+
type FolderConfirmDelete = ReturnType<typeof FolderConfirmDelete>;
|
|
12
|
+
export default FolderConfirmDelete;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export declare const PENDING_SCOPE_KEY = "sh3:pending-scope";
|
|
2
|
+
export declare function readPendingScope(): string | null;
|
|
3
|
+
export declare function switchProjectScope(projectId: string | null): void;
|
|
1
4
|
export declare const sessionState: {
|
|
2
5
|
activeProjectId: string | null;
|
|
3
6
|
};
|
|
@@ -12,6 +12,31 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { activeApp, breadcrumbApp } from '../apps/registry.svelte';
|
|
14
14
|
import { unloadApp } from '../apps/lifecycle';
|
|
15
|
+
export const PENDING_SCOPE_KEY = 'sh3:pending-scope';
|
|
16
|
+
export function readPendingScope() {
|
|
17
|
+
if (typeof sessionStorage === 'undefined')
|
|
18
|
+
return null;
|
|
19
|
+
const raw = sessionStorage.getItem(PENDING_SCOPE_KEY);
|
|
20
|
+
if (!raw)
|
|
21
|
+
return null;
|
|
22
|
+
sessionStorage.removeItem(PENDING_SCOPE_KEY);
|
|
23
|
+
try {
|
|
24
|
+
const parsed = JSON.parse(raw);
|
|
25
|
+
return typeof parsed.projectId === 'string' ? parsed.projectId : null;
|
|
26
|
+
}
|
|
27
|
+
catch (_a) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function switchProjectScope(projectId) {
|
|
32
|
+
if (projectId !== null) {
|
|
33
|
+
sessionStorage.setItem(PENDING_SCOPE_KEY, JSON.stringify({ projectId }));
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
sessionStorage.removeItem(PENDING_SCOPE_KEY);
|
|
37
|
+
}
|
|
38
|
+
window.location.reload();
|
|
39
|
+
}
|
|
15
40
|
export const sessionState = $state({
|
|
16
41
|
activeProjectId: null,
|
|
17
42
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { sessionState, setActiveProjectId } from './session-state.svelte';
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { sessionState, setActiveProjectId, switchProjectScope, readPendingScope, PENDING_SCOPE_KEY, } from './session-state.svelte';
|
|
3
3
|
import { breadcrumbApp, activeApp } from '../apps/registry.svelte';
|
|
4
4
|
vi.mock('../apps/lifecycle', () => ({
|
|
5
5
|
unloadApp: vi.fn(),
|
|
@@ -53,3 +53,44 @@ describe('sessionState.activeProjectId', () => {
|
|
|
53
53
|
expect(lifecycle.unloadApp).not.toHaveBeenCalled();
|
|
54
54
|
});
|
|
55
55
|
});
|
|
56
|
+
describe('readPendingScope', () => {
|
|
57
|
+
beforeEach(() => sessionStorage.clear());
|
|
58
|
+
it('returns null when nothing is stored', () => {
|
|
59
|
+
expect(readPendingScope()).toBeNull();
|
|
60
|
+
});
|
|
61
|
+
it('returns the stored projectId and clears the key', () => {
|
|
62
|
+
sessionStorage.setItem(PENDING_SCOPE_KEY, JSON.stringify({ projectId: 'proj-abc' }));
|
|
63
|
+
expect(readPendingScope()).toBe('proj-abc');
|
|
64
|
+
expect(sessionStorage.getItem(PENDING_SCOPE_KEY)).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
it('returns null and clears a malformed entry', () => {
|
|
67
|
+
sessionStorage.setItem(PENDING_SCOPE_KEY, 'not-json');
|
|
68
|
+
expect(readPendingScope()).toBeNull();
|
|
69
|
+
expect(sessionStorage.getItem(PENDING_SCOPE_KEY)).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
it('returns null if projectId field is not a string', () => {
|
|
72
|
+
sessionStorage.setItem(PENDING_SCOPE_KEY, JSON.stringify({ other: 42 }));
|
|
73
|
+
expect(readPendingScope()).toBeNull();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('switchProjectScope', () => {
|
|
77
|
+
let reloadSpy;
|
|
78
|
+
beforeEach(() => {
|
|
79
|
+
sessionStorage.clear();
|
|
80
|
+
reloadSpy = vi.fn();
|
|
81
|
+
vi.stubGlobal('location', { reload: reloadSpy });
|
|
82
|
+
});
|
|
83
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
84
|
+
it('writes projectId to sessionStorage and reloads', () => {
|
|
85
|
+
switchProjectScope('proj-abc');
|
|
86
|
+
const raw = sessionStorage.getItem(PENDING_SCOPE_KEY);
|
|
87
|
+
expect(JSON.parse(raw)).toEqual({ projectId: 'proj-abc' });
|
|
88
|
+
expect(reloadSpy).toHaveBeenCalledOnce();
|
|
89
|
+
});
|
|
90
|
+
it('removes the key for null (personal scope) and reloads', () => {
|
|
91
|
+
sessionStorage.setItem(PENDING_SCOPE_KEY, JSON.stringify({ projectId: 'old' }));
|
|
92
|
+
switchProjectScope(null);
|
|
93
|
+
expect(sessionStorage.getItem(PENDING_SCOPE_KEY)).toBeNull();
|
|
94
|
+
expect(reloadSpy).toHaveBeenCalledOnce();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -1,26 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
/*
|
|
3
|
-
* Projects section for Sh3Home.
|
|
4
|
-
*
|
|
5
|
-
* Renders the list of projects the current user is a member of as
|
|
6
|
-
* selectable cards. Selecting a project sets sessionState.activeProjectId
|
|
7
|
-
* (which then filters the apps grid via the appAllowlist) and binds any
|
|
8
|
-
* subsequently launched app's documents to the project scope.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
2
|
import { projectsState, openProjectManage } from './projectsShard.svelte';
|
|
12
|
-
import { sessionState,
|
|
3
|
+
import { sessionState, switchProjectScope } from '../projects/session-state.svelte';
|
|
13
4
|
import { isAdmin } from '../auth/auth.svelte';
|
|
14
5
|
import HomeSection from '../sh3core-shard/HomeSection.svelte';
|
|
6
|
+
import Button from '../primitives/Button.svelte';
|
|
15
7
|
|
|
16
8
|
const visible = $derived(projectsState.projects.length > 0);
|
|
17
9
|
const activeId = $derived(sessionState.activeProjectId);
|
|
18
10
|
const elevated = $derived(isAdmin());
|
|
19
|
-
|
|
20
|
-
function selectProject(id: string) {
|
|
21
|
-
setActiveProjectId(activeId === id ? null : id);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
11
|
function editProject(id: string, ev: MouseEvent) {
|
|
25
12
|
ev.stopPropagation();
|
|
26
13
|
const project = projectsState.projects.find((p) => p.id === id) ?? null;
|
|
@@ -28,7 +15,13 @@
|
|
|
28
15
|
}
|
|
29
16
|
</script>
|
|
30
17
|
|
|
31
|
-
{#if
|
|
18
|
+
{#if activeId !== null}
|
|
19
|
+
<HomeSection title="Project" persistKey="project-active">
|
|
20
|
+
<div class="leave-project">
|
|
21
|
+
<Button onclick={() => switchProjectScope(null)}>Leave project</Button>
|
|
22
|
+
</div>
|
|
23
|
+
</HomeSection>
|
|
24
|
+
{:else if visible}
|
|
32
25
|
<HomeSection title="Projects" persistKey="projects">
|
|
33
26
|
<div class="projects-grid">
|
|
34
27
|
{#each projectsState.projects as project (project.id)}
|
|
@@ -36,8 +29,7 @@
|
|
|
36
29
|
<button
|
|
37
30
|
type="button"
|
|
38
31
|
class="project-card"
|
|
39
|
-
|
|
40
|
-
onclick={() => selectProject(project.id)}
|
|
32
|
+
onclick={() => switchProjectScope(project.id)}
|
|
41
33
|
title={project.description ?? `${project.members.length} member${project.members.length === 1 ? '' : 's'}`}
|
|
42
34
|
>
|
|
43
35
|
<span class="project-name">{project.name}</span>
|
|
@@ -104,4 +96,8 @@
|
|
|
104
96
|
}
|
|
105
97
|
.project-name { font-weight: 600; font-size: 13px; }
|
|
106
98
|
.project-meta { font-size: 11px; color: var(--sh3-fg-muted); }
|
|
99
|
+
.leave-project {
|
|
100
|
+
display: flex;
|
|
101
|
+
justify-content: flex-start;
|
|
102
|
+
}
|
|
107
103
|
</style>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
13
13
|
import { MemoryDocumentBackend } from '../documents/backends';
|
|
14
|
-
import { __setDocumentBackend,
|
|
14
|
+
import { __setDocumentBackend, __setActiveScope } from '../documents/config';
|
|
15
15
|
import { registerShard, activateShard, __resetShardRegistryForTest, } from '../shards/activate.svelte';
|
|
16
16
|
import { __resetViewRegistryForTest } from '../shards/registry';
|
|
17
17
|
import { __resetActionsRegistryForTest } from '../actions/registry';
|
|
@@ -25,7 +25,7 @@ describe('shell-shard programmatic verbs (integration)', () => {
|
|
|
25
25
|
__resetActionsRegistryForTest();
|
|
26
26
|
__resetAppRegistryForTest();
|
|
27
27
|
__setDocumentBackend(new MemoryDocumentBackend());
|
|
28
|
-
|
|
28
|
+
__setActiveScope('tenant-test');
|
|
29
29
|
registerShard(shellShard);
|
|
30
30
|
await activateShard('shell');
|
|
31
31
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
2
|
import { MemoryDocumentBackend } from '../documents/backends';
|
|
3
|
-
import { __setDocumentBackend,
|
|
3
|
+
import { __setDocumentBackend, __setActiveScope } from '../documents/config';
|
|
4
4
|
import { registerShard, activateShard, __resetShardRegistryForTest, } from '../shards/activate.svelte';
|
|
5
5
|
import { __resetViewRegistryForTest } from '../shards/registry';
|
|
6
6
|
import { runVerbProgrammatic } from './runVerb';
|
|
@@ -19,7 +19,7 @@ describe('runVerbProgrammatic', () => {
|
|
|
19
19
|
__resetShardRegistryForTest();
|
|
20
20
|
__resetViewRegistryForTest();
|
|
21
21
|
__setDocumentBackend(new MemoryDocumentBackend());
|
|
22
|
-
|
|
22
|
+
__setActiveScope('tenant-test');
|
|
23
23
|
});
|
|
24
24
|
it('rejects on unknown shard', async () => {
|
|
25
25
|
await expect(runVerbProgrammatic('missing', 'echo', [])).rejects.toThrow('unknown shard: missing');
|
package/dist/sh3Api/headless.js
CHANGED
|
@@ -31,6 +31,8 @@ import { listFields as listFieldsImpl, getField as getFieldImpl, setField as set
|
|
|
31
31
|
import { attachDecoration as attachDecorationImpl } from '../fields/decoration';
|
|
32
32
|
import { onChange as onContributionsChange } from '../contributions';
|
|
33
33
|
import { FIELD_POINT_ID, WALKER_SHARD_ID } from '../fields/types';
|
|
34
|
+
import { getActiveScopeId, getPersonalScopeId } from '../documents/config';
|
|
35
|
+
import { projectsState } from '../projects-shard/projectsShard.svelte';
|
|
34
36
|
const KNOWN_ZONES = ['ephemeral', 'session', 'workspace', 'user'];
|
|
35
37
|
function collectTabEntries(node) {
|
|
36
38
|
if (node.type === 'tabs') {
|
|
@@ -317,6 +319,14 @@ export function makeSh3Api(opts) {
|
|
|
317
319
|
return listViewsImpl();
|
|
318
320
|
},
|
|
319
321
|
fields,
|
|
322
|
+
getActiveScope() {
|
|
323
|
+
const id = getActiveScopeId();
|
|
324
|
+
const personalId = getPersonalScopeId();
|
|
325
|
+
return { id, isProject: id !== personalId, personalId };
|
|
326
|
+
},
|
|
327
|
+
listProjects() {
|
|
328
|
+
return projectsState.projects.map((p) => ({ id: p.id, name: p.name }));
|
|
329
|
+
},
|
|
320
330
|
};
|
|
321
331
|
}
|
|
322
332
|
/** @deprecated Renamed to makeSh3Api(opts?). Kept for one minor cycle. */
|
|
@@ -30,6 +30,7 @@ import AppInfoView from './AppInfoView.svelte';
|
|
|
30
30
|
import { spawnSatellite } from '../sh3Api/window';
|
|
31
31
|
import { activeApp, getActiveApp } from '../apps/registry.svelte';
|
|
32
32
|
import { returnToHome } from '../apps/lifecycle';
|
|
33
|
+
import { sessionState } from '../projects/session-state.svelte';
|
|
33
34
|
const isTauri = typeof globalThis.__TAURI_INTERNALS__ !== 'undefined';
|
|
34
35
|
export function computeAppActionDisabled(g) {
|
|
35
36
|
return !g.admin || g.builtin;
|
|
@@ -111,7 +112,7 @@ async function runCheckUpdate(_ctx) {
|
|
|
111
112
|
modalManager.open(AppUpdateAvailableModal, props);
|
|
112
113
|
}
|
|
113
114
|
function runPopOut(_ctx) {
|
|
114
|
-
var _a;
|
|
115
|
+
var _a, _b;
|
|
115
116
|
const ref = readSelection();
|
|
116
117
|
if (!ref)
|
|
117
118
|
return;
|
|
@@ -122,10 +123,11 @@ function runPopOut(_ctx) {
|
|
|
122
123
|
kind: 'app',
|
|
123
124
|
appId: ref.appId,
|
|
124
125
|
activateShards: (_a = manifest.requiredShards) !== null && _a !== void 0 ? _a : [],
|
|
126
|
+
projectId: (_b = sessionState.activeProjectId) !== null && _b !== void 0 ? _b : undefined,
|
|
125
127
|
});
|
|
126
128
|
}
|
|
127
129
|
async function runPopOutCurrent(_ctx) {
|
|
128
|
-
var _a;
|
|
130
|
+
var _a, _b;
|
|
129
131
|
const current = getActiveApp();
|
|
130
132
|
if (!current)
|
|
131
133
|
return;
|
|
@@ -136,6 +138,7 @@ async function runPopOutCurrent(_ctx) {
|
|
|
136
138
|
kind: 'app',
|
|
137
139
|
appId,
|
|
138
140
|
activateShards: requiredShards,
|
|
141
|
+
projectId: (_b = sessionState.activeProjectId) !== null && _b !== void 0 ? _b : undefined,
|
|
139
142
|
});
|
|
140
143
|
}
|
|
141
144
|
function runUninstall(_ctx) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
2
|
import { MemoryDocumentBackend } from '../documents/backends';
|
|
3
|
-
import { __setDocumentBackend,
|
|
3
|
+
import { __setDocumentBackend, __setActiveScope } from '../documents/config';
|
|
4
4
|
import { registerShard, activateShard, __resetShardRegistryForTest } from './activate.svelte';
|
|
5
5
|
import { PERMISSION_DOCUMENTS_BROWSE, PERMISSION_DOCUMENTS_READ, PERMISSION_DOCUMENTS_WRITE, } from '../documents/types';
|
|
6
6
|
describe('ctx.browse permission gating', () => {
|
|
7
7
|
beforeEach(() => {
|
|
8
8
|
__resetShardRegistryForTest();
|
|
9
9
|
__setDocumentBackend(new MemoryDocumentBackend());
|
|
10
|
-
|
|
10
|
+
__setActiveScope('tenant-a');
|
|
11
11
|
});
|
|
12
12
|
it('is undefined when no documents permission is declared', async () => {
|
|
13
13
|
let captured = null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
2
|
import { MemoryDocumentBackend } from '../documents/backends';
|
|
3
|
-
import { __setDocumentBackend,
|
|
3
|
+
import { __setDocumentBackend, __setActiveScope } from '../documents/config';
|
|
4
4
|
import { registerShard, activateShard, deactivateShard, __resetShardRegistryForTest, } from './activate.svelte';
|
|
5
5
|
import { __resetContributionsForTest, list, listPoints } from '../contributions';
|
|
6
6
|
describe('ctx.contributions', () => {
|
|
@@ -8,7 +8,7 @@ describe('ctx.contributions', () => {
|
|
|
8
8
|
__resetShardRegistryForTest();
|
|
9
9
|
__resetContributionsForTest();
|
|
10
10
|
__setDocumentBackend(new MemoryDocumentBackend());
|
|
11
|
-
|
|
11
|
+
__setActiveScope('tenant-a');
|
|
12
12
|
});
|
|
13
13
|
it('is always present on ShardContext (no permission required)', async () => {
|
|
14
14
|
let captured = null;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
2
|
import { MemoryDocumentBackend } from '../documents/backends';
|
|
3
|
-
import { __setDocumentBackend,
|
|
3
|
+
import { __setDocumentBackend, __setActiveScope } from '../documents/config';
|
|
4
4
|
import { registerShard, activateShard, registeredShards, activeShards, __resetShardRegistryForTest, erroredShards, } from './activate.svelte';
|
|
5
5
|
describe('erroredShards map', () => {
|
|
6
6
|
beforeEach(() => {
|
|
7
7
|
__resetShardRegistryForTest();
|
|
8
8
|
__setDocumentBackend(new MemoryDocumentBackend());
|
|
9
|
-
|
|
9
|
+
__setActiveScope('tenant-a');
|
|
10
10
|
});
|
|
11
11
|
it('is empty after reset', () => {
|
|
12
12
|
expect(erroredShards.size).toBe(0);
|
|
@@ -21,7 +21,7 @@ describe('activateShard — unwind on activation failure', () => {
|
|
|
21
21
|
beforeEach(() => {
|
|
22
22
|
__resetShardRegistryForTest();
|
|
23
23
|
__setDocumentBackend(new MemoryDocumentBackend());
|
|
24
|
-
|
|
24
|
+
__setActiveScope('tenant-a');
|
|
25
25
|
});
|
|
26
26
|
it('unwinds partial state and records the error when activate throws', async () => {
|
|
27
27
|
const shard = {
|