@fias/arche-sdk 2.30.0 → 2.32.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.
@@ -299,6 +299,15 @@ describe('SDK hooks', () => {
299
299
  chrome.openAssistant();
300
300
  expect(mockBridge.openAssistant).toHaveBeenCalled();
301
301
  });
302
+ it('carries where the person came from, null when nothing opened the arche', () => {
303
+ const { useFiasChrome } = require('./hooks');
304
+ expect(useFiasChrome().opener).toBeNull();
305
+ mockBridge.getChrome.mockReturnValue({
306
+ ...HOST_STATE,
307
+ opener: { kind: 'forge', name: 'My Arche' },
308
+ });
309
+ expect(useFiasChrome().opener).toEqual({ kind: 'forge', name: 'My Arche' });
310
+ });
302
311
  it('cleanup unsubscribes from chrome updates', () => {
303
312
  const unsubscribe = jest.fn();
304
313
  mockBridge.onChromeUpdate.mockReturnValue(unsubscribe);
@@ -854,6 +863,116 @@ describe('SDK hooks', () => {
854
863
  expect(mockBridge.request).toHaveBeenCalledWith(...entityOp(entity_ops_1.PLUGIN_WORKSPACES_ENTITY_ID, 'create', { forActingHolder: true }));
855
864
  });
856
865
  });
866
+ describe('useFiasWorkspaces() member targets (ADR-040 §4)', () => {
867
+ it('updateMember / removeMember send a ref target as { ref }, a string as { username }', async () => {
868
+ const { useFiasWorkspaces } = require('./hooks');
869
+ mockBridge.request.mockResolvedValue(output({ member: {} }));
870
+ await useFiasWorkspaces().updateMember('ws_1', { ref: 'aur_a' }, 'admin');
871
+ expect(mockBridge.request).toHaveBeenLastCalledWith(...entityOp(entity_ops_1.PLUGIN_WORKSPACES_ENTITY_ID, 'update_member', {
872
+ workspaceId: 'ws_1',
873
+ ref: 'aur_a',
874
+ role: 'admin',
875
+ }));
876
+ mockBridge.request.mockResolvedValue(output({}));
877
+ await useFiasWorkspaces().removeMember('ws_1', 'alice');
878
+ expect(mockBridge.request).toHaveBeenLastCalledWith(...entityOp(entity_ops_1.PLUGIN_WORKSPACES_ENTITY_ID, 'remove_member', {
879
+ workspaceId: 'ws_1',
880
+ username: 'alice',
881
+ }));
882
+ });
883
+ });
884
+ describe('useFiasWorkspaces() member target guard (ADR-040 §4)', () => {
885
+ it('a missing target (m.username on a ref roster) is a clear error, not a TypeError', async () => {
886
+ const { useFiasWorkspaces } = require('./hooks');
887
+ await expect(useFiasWorkspaces().removeMember('ws_1', undefined)).rejects.toThrow(/name the member by \{ ref \}/);
888
+ expect(mockBridge.request).not.toHaveBeenCalled();
889
+ });
890
+ });
891
+ describe('useFiasWorkspaces().resolvePeople (ADR-040)', () => {
892
+ it('sends the refs and the workspace, and returns the names or nulls in order', async () => {
893
+ const { useFiasWorkspaces } = require('./hooks');
894
+ const people = [{ ref: 'aur_a', displayName: 'Ada', avatar: null }, null];
895
+ mockBridge.request.mockResolvedValue(output({ people }));
896
+ await expect(useFiasWorkspaces().resolvePeople(['aur_a', 'aur_b'], 'ws_1')).resolves.toEqual(people);
897
+ expect(mockBridge.request).toHaveBeenCalledWith(...entityOp(entity_ops_1.PLUGIN_WORKSPACES_ENTITY_ID, 'resolve_people', {
898
+ refs: ['aur_a', 'aur_b'],
899
+ workspaceId: 'ws_1',
900
+ }));
901
+ });
902
+ it('an empty list answers [] without a call', async () => {
903
+ const { useFiasWorkspaces } = require('./hooks');
904
+ await expect(useFiasWorkspaces().resolvePeople([], 'ws_1')).resolves.toEqual([]);
905
+ expect(mockBridge.request).not.toHaveBeenCalled();
906
+ });
907
+ it('pages more than 100 refs and keeps their order', async () => {
908
+ const { useFiasWorkspaces } = require('./hooks');
909
+ const refs = Array.from({ length: 150 }, (_, i) => `aur_${i}`);
910
+ mockBridge.request
911
+ .mockResolvedValueOnce(output({ people: refs.slice(0, 100).map(() => null) }))
912
+ .mockResolvedValueOnce(output({
913
+ people: refs.slice(100).map((ref) => ({ ref, displayName: ref, avatar: null })),
914
+ }));
915
+ const people = await useFiasWorkspaces().resolvePeople(refs, 'ws_1');
916
+ expect(mockBridge.request).toHaveBeenCalledTimes(2);
917
+ expect(people).toHaveLength(150);
918
+ expect(people[99]).toBeNull();
919
+ expect(people[149]).toMatchObject({ ref: 'aur_149' });
920
+ });
921
+ it('omits workspaceId when none is given (own ref only)', async () => {
922
+ const { useFiasWorkspaces } = require('./hooks');
923
+ mockBridge.request.mockResolvedValue(output({ people: [null] }));
924
+ await useFiasWorkspaces().resolvePeople(['aur_a']);
925
+ expect(mockBridge.request).toHaveBeenCalledWith(...entityOp(entity_ops_1.PLUGIN_WORKSPACES_ENTITY_ID, 'resolve_people', { refs: ['aur_a'] }));
926
+ });
927
+ });
928
+ describe('useFiasWorkspaces().addPeople (ADR-040 Q12)', () => {
929
+ it('opens the host sheet with the workspace and suggested role, and returns who was added', async () => {
930
+ const { useFiasWorkspaces } = require('./hooks');
931
+ const added = [{ ref: 'aur_abcdefghijklmnopqrst', displayName: 'Ada', role: 'viewer' }];
932
+ mockBridge.request.mockResolvedValue({ added });
933
+ await expect(useFiasWorkspaces().addPeople('ws_1', { role: 'viewer' })).resolves.toEqual({
934
+ added,
935
+ });
936
+ expect(mockBridge.request).toHaveBeenCalledWith('client_entity_invoke', {
937
+ entityId: 'ent_intg_workspace_add_people_00',
938
+ input: { workspaceId: 'ws_1', role: 'viewer' },
939
+ }, expect.any(Number));
940
+ });
941
+ it("the host's typed refusal reaches the app as its code", async () => {
942
+ const { useFiasWorkspaces } = require('./hooks');
943
+ mockBridge.request.mockResolvedValue({
944
+ error: { code: 'SHEET_BUSY', message: 'Another request is waiting' },
945
+ });
946
+ await expect(useFiasWorkspaces().addPeople('ws_1')).rejects.toMatchObject({
947
+ code: 'SHEET_BUSY',
948
+ });
949
+ });
950
+ it('a host without the sheet answers ADD_PEOPLE_UNAVAILABLE', async () => {
951
+ const { useFiasWorkspaces } = require('./hooks');
952
+ const { FiasBridgeError } = jest.requireActual('./bridge');
953
+ mockBridge.request.mockRejectedValue(new FiasBridgeError('client_entity_invoke: no client-side executor registered for ent_intg_workspace_add_people_00', 'BRIDGE_ERROR'));
954
+ await expect(useFiasWorkspaces().addPeople('ws_1')).rejects.toMatchObject({
955
+ code: 'ADD_PEOPLE_UNAVAILABLE',
956
+ });
957
+ });
958
+ it('recognises the host code CLIENT_ENTITY_UNAVAILABLE, whatever the message', async () => {
959
+ const { useFiasWorkspaces } = require('./hooks');
960
+ const { FiasBridgeError } = jest.requireActual('./bridge');
961
+ mockBridge.request.mockRejectedValue(new FiasBridgeError('Not here', 'CLIENT_ENTITY_UNAVAILABLE'));
962
+ await expect(useFiasWorkspaces().addPeople('ws_1')).rejects.toMatchObject({
963
+ code: 'ADD_PEOPLE_UNAVAILABLE',
964
+ });
965
+ });
966
+ it('passes any other bridge error through unchanged', async () => {
967
+ const { useFiasWorkspaces } = require('./hooks');
968
+ const { FiasBridgeError } = jest.requireActual('./bridge');
969
+ mockBridge.request.mockRejectedValue(new FiasBridgeError('Permission denied: data:workspace', 'BRIDGE_ERROR'));
970
+ await expect(useFiasWorkspaces().addPeople('ws_1')).rejects.toMatchObject({
971
+ code: 'BRIDGE_ERROR',
972
+ message: 'Permission denied: data:workspace',
973
+ });
974
+ });
975
+ });
857
976
  // ADR-034 phase 0b. The real-render cases (stale answers across workspace
858
977
  // switches, the render before effects run, refetch) live in
859
978
  // use-fias-access.render.test.tsx, which CI runs through the package's
@@ -1015,7 +1134,7 @@ describe('SDK hooks', () => {
1015
1134
  await expect(state.openAccessDialog()).rejects.toMatchObject({ code: 'NOT_IN_WORKSPACE' });
1016
1135
  });
1017
1136
  });
1018
- describe('useCreativeLibrary().pick (ADR-035 D8)', () => {
1137
+ describe('useCreativeLibrary().pick (ADR-037 D8)', () => {
1019
1138
  const PICKED = {
1020
1139
  item: { itemId: 'vci_' + 'a'.repeat(32), kind: 'style', name: 'Ink', version: 2 },
1021
1140
  };
@@ -1053,6 +1172,71 @@ describe('SDK hooks', () => {
1053
1172
  });
1054
1173
  });
1055
1174
  });
1175
+ describe('useOpener (ADR-037 D8)', () => {
1176
+ const FILE = 'doc_' + 'a'.repeat(32);
1177
+ const opener = { kind: 'forge', name: 'My Arche' };
1178
+ const CHROME = {
1179
+ focusMode: false,
1180
+ focusModeAvailable: false,
1181
+ sidebarAvailable: false,
1182
+ sidebarCollapsed: false,
1183
+ hostExpandedBar: true,
1184
+ hostExpandButton: true,
1185
+ opener,
1186
+ };
1187
+ it('names where the person came from, without taking the chrome over', () => {
1188
+ mockBridge.getChrome.mockReturnValue(CHROME);
1189
+ const { useOpener } = require('./hooks');
1190
+ expect(useOpener().opener).toEqual(opener);
1191
+ expect(mockBridge.setChrome).not.toHaveBeenCalled();
1192
+ });
1193
+ it('returns there only when there is an opener', () => {
1194
+ const { useOpener } = require('./hooks');
1195
+ useOpener().returnTo();
1196
+ expect(mockBridge.navigateHost).not.toHaveBeenCalled();
1197
+ mockBridge.getChrome.mockReturnValue(CHROME);
1198
+ useOpener().returnTo();
1199
+ expect(mockBridge.navigateHost).toHaveBeenCalledWith('opener');
1200
+ });
1201
+ it('offers a file through the host’s sheet, waiting on the person', async () => {
1202
+ mockBridge.getChrome.mockReturnValue(CHROME);
1203
+ mockBridge.request.mockResolvedValue({ sent: true, name: 'pumpkin.png' });
1204
+ const { useOpener } = require('./hooks');
1205
+ await expect(useOpener().send({ fileId: FILE, name: 'pumpkin.png' })).resolves.toEqual({
1206
+ sent: true,
1207
+ name: 'pumpkin.png',
1208
+ });
1209
+ const [op, payload, timeout] = mockBridge.request.mock.calls[0];
1210
+ expect(op).toBe('client_entity_invoke');
1211
+ expect(payload).toEqual({
1212
+ entityId: entity_ops_1.SEND_TO_OPENER_ENTITY_ID,
1213
+ input: { fileId: FILE, name: 'pumpkin.png' },
1214
+ });
1215
+ expect(timeout).toBeGreaterThanOrEqual(5 * 60 * 1000);
1216
+ });
1217
+ it('refuses with NO_OPENER when nothing opened this arche, asking nothing', async () => {
1218
+ const { useOpener } = require('./hooks');
1219
+ const api = useOpener();
1220
+ expect(api.opener).toBeNull();
1221
+ await expect(api.send({ fileId: FILE })).rejects.toMatchObject({ code: 'NO_OPENER' });
1222
+ expect(mockBridge.request).not.toHaveBeenCalled();
1223
+ });
1224
+ it('turns a refusal into a typed error, and a host without it into SEND_UNAVAILABLE', async () => {
1225
+ mockBridge.getChrome.mockReturnValue(CHROME);
1226
+ const { useOpener } = require('./hooks');
1227
+ mockBridge.request.mockResolvedValueOnce({
1228
+ error: { code: 'SHEET_BUSY', message: 'Another request is waiting' },
1229
+ });
1230
+ await expect(useOpener().send({ fileId: FILE })).rejects.toMatchObject({
1231
+ code: 'SHEET_BUSY',
1232
+ });
1233
+ const { FiasBridgeError } = jest.requireActual('./bridge');
1234
+ mockBridge.request.mockRejectedValueOnce(new FiasBridgeError('no client-side executor registered for x', 'BRIDGE_ERROR'));
1235
+ await expect(useOpener().send({ fileId: FILE })).rejects.toMatchObject({
1236
+ code: 'SEND_UNAVAILABLE',
1237
+ });
1238
+ });
1239
+ });
1056
1240
  describe('useFiasConnections', () => {
1057
1241
  it('lists through the connections entity', async () => {
1058
1242
  const { useFiasConnections } = require('./hooks');