@memberjunction/ng-explorer-settings 5.48.0 → 5.49.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-explorer-settings",
3
- "version": "5.48.0",
3
+ "version": "5.49.0",
4
4
  "description": "MemberJunction: Reusable Angular components for the settings section of the MJ Explorer App",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -26,7 +26,8 @@
26
26
  "license": "ISC",
27
27
  "devDependencies": {
28
28
  "@angular/compiler": "21.1.3",
29
- "@angular/compiler-cli": "21.1.3"
29
+ "@angular/compiler-cli": "21.1.3",
30
+ "@memberjunction/ng-test-utils": "5.49.0"
30
31
  },
31
32
  "peerDependencies": {
32
33
  "@angular/common": "21.1.3",
@@ -36,24 +37,24 @@
36
37
  },
37
38
  "dependencies": {
38
39
  "@angular/cdk": "21.1.3",
39
- "@memberjunction/core": "5.48.0",
40
- "@memberjunction/core-entities": "5.48.0",
41
- "@memberjunction/global": "5.48.0",
42
- "@memberjunction/graphql-dataprovider": "5.48.0",
43
- "@memberjunction/ng-base-application": "5.48.0",
44
- "@memberjunction/ng-base-forms": "5.48.0",
45
- "@memberjunction/ng-base-types": "5.48.0",
46
- "@memberjunction/ng-code-editor": "5.48.0",
47
- "@memberjunction/ng-entity-form-dialog": "5.48.0",
48
- "@memberjunction/ng-entity-permissions": "5.48.0",
49
- "@memberjunction/ng-join-grid": "5.48.0",
50
- "@memberjunction/ng-notifications": "5.48.0",
51
- "@memberjunction/ng-shared": "5.48.0",
52
- "@memberjunction/ng-shared-generic": "5.48.0",
53
- "@memberjunction/ng-simple-record-list": "5.48.0",
54
- "@memberjunction/ng-tabstrip": "5.48.0",
55
- "@memberjunction/ng-ui-components": "5.48.0",
56
- "@memberjunction/ng-user-avatar": "5.48.0",
40
+ "@memberjunction/core": "5.49.0",
41
+ "@memberjunction/core-entities": "5.49.0",
42
+ "@memberjunction/global": "5.49.0",
43
+ "@memberjunction/graphql-dataprovider": "5.49.0",
44
+ "@memberjunction/ng-base-application": "5.49.0",
45
+ "@memberjunction/ng-base-forms": "5.49.0",
46
+ "@memberjunction/ng-base-types": "5.49.0",
47
+ "@memberjunction/ng-code-editor": "5.49.0",
48
+ "@memberjunction/ng-entity-form-dialog": "5.49.0",
49
+ "@memberjunction/ng-entity-permissions": "5.49.0",
50
+ "@memberjunction/ng-join-grid": "5.49.0",
51
+ "@memberjunction/ng-notifications": "5.49.0",
52
+ "@memberjunction/ng-shared": "5.49.0",
53
+ "@memberjunction/ng-shared-generic": "5.49.0",
54
+ "@memberjunction/ng-simple-record-list": "5.49.0",
55
+ "@memberjunction/ng-tabstrip": "5.49.0",
56
+ "@memberjunction/ng-ui-components": "5.49.0",
57
+ "@memberjunction/ng-user-avatar": "5.49.0",
57
58
  "rxjs": "~7.8.2",
58
59
  "tslib": "^2.8.1"
59
60
  },
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=explorer-settings.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"explorer-settings.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/explorer-settings.test.ts"],"names":[],"mappings":""}
@@ -1,23 +0,0 @@
1
- /**
2
- * Tests for explorer-settings package:
3
- * - Module export verification
4
- */
5
- import { describe, it, expect, vi } from 'vitest';
6
- vi.mock('@angular/core', () => ({
7
- Component: () => (target) => target,
8
- NgModule: () => (target) => target,
9
- Injectable: () => (target) => target,
10
- Input: () => () => { },
11
- Output: () => () => { },
12
- EventEmitter: class {
13
- emit() { }
14
- },
15
- ChangeDetectorRef: class {
16
- },
17
- }));
18
- describe('explorer-settings package', () => {
19
- it('should be a valid module', () => {
20
- expect(true).toBe(true);
21
- });
22
- });
23
- //# sourceMappingURL=explorer-settings.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"explorer-settings.test.js","sourceRoot":"","sources":["../../src/__tests__/explorer-settings.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAElD,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9B,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,MAAgB,EAAE,EAAE,CAAC,MAAM;IAC7C,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,MAAgB,EAAE,EAAE,CAAC,MAAM;IAC5C,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,MAAgB,EAAE,EAAE,CAAC,MAAM;IAC9C,KAAK,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC;IACrB,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC;IACtB,YAAY,EAAE;QAAQ,IAAI,KAAI,CAAC;KAAE;IACjC,iBAAiB,EAAE;KAAQ;CAC5B,CAAC,CAAC,CAAC;AAEJ,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IACzC,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["/**\n * Tests for explorer-settings package:\n * - Module export verification\n */\nimport { describe, it, expect, vi } from 'vitest';\n\nvi.mock('@angular/core', () => ({\n Component: () => (target: Function) => target,\n NgModule: () => (target: Function) => target,\n Injectable: () => (target: Function) => target,\n Input: () => () => {},\n Output: () => () => {},\n EventEmitter: class { emit() {} },\n ChangeDetectorRef: class {},\n}));\n\ndescribe('explorer-settings package', () => {\n it('should be a valid module', () => {\n expect(true).toBe(true);\n });\n});\n"]}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=role-management-agent-context.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"role-management-agent-context.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/role-management-agent-context.test.ts"],"names":[],"mappings":""}
@@ -1,107 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { buildRoleManagementAgentContext, isValidRoleTypeFilter, capRoleNames, resolveRoleByIDOrName, ROLE_AGENT_CONTEXT_NAME_LIST_CAP, } from '../lib/role-management/role-management-agent-context';
3
- const baseInput = () => ({
4
- TotalRoleCount: 4,
5
- FilteredRoleCount: 2,
6
- SystemRoleCount: 2,
7
- CustomRoleCount: 2,
8
- TypeFilter: 'all',
9
- SearchText: '',
10
- SelectedRoleId: null,
11
- SelectedRoleName: null,
12
- VisibleRoleNames: ['Administrator', 'Developer'],
13
- SelectedRolePermissions: null,
14
- });
15
- const roles = [
16
- { ID: 'R1', Name: 'Administrator' },
17
- { ID: 'R2', Name: 'Developer' },
18
- { ID: 'R3', Name: 'Read Only' },
19
- ];
20
- const summary = {
21
- EntityCount: 10,
22
- ReadCount: 10,
23
- CreateCount: 3,
24
- UpdateCount: 3,
25
- DeleteCount: 1,
26
- };
27
- describe('isValidRoleTypeFilter', () => {
28
- it('accepts the three valid filters', () => {
29
- expect(isValidRoleTypeFilter('all')).toBe(true);
30
- expect(isValidRoleTypeFilter('system')).toBe(true);
31
- expect(isValidRoleTypeFilter('custom')).toBe(true);
32
- });
33
- it('rejects anything else', () => {
34
- expect(isValidRoleTypeFilter('System')).toBe(false);
35
- expect(isValidRoleTypeFilter('')).toBe(false);
36
- expect(isValidRoleTypeFilter(7)).toBe(false);
37
- });
38
- });
39
- describe('capRoleNames', () => {
40
- it('caps at the configured limit', () => {
41
- const many = Array.from({ length: ROLE_AGENT_CONTEXT_NAME_LIST_CAP + 3 }, (_, i) => `r${i}`);
42
- expect(capRoleNames(many)).toHaveLength(ROLE_AGENT_CONTEXT_NAME_LIST_CAP);
43
- });
44
- });
45
- describe('resolveRoleByIDOrName', () => {
46
- it('matches by exact ID (case-insensitive)', () => {
47
- const r = resolveRoleByIDOrName('r1', roles);
48
- expect(r.ok && r.match.Name).toBe('Administrator');
49
- });
50
- it('matches by exact name', () => {
51
- const r = resolveRoleByIDOrName('Developer', roles);
52
- expect(r.ok && r.match.ID).toBe('R2');
53
- });
54
- it('falls back to contains', () => {
55
- const r = resolveRoleByIDOrName('read', roles);
56
- expect(r.ok && r.match.Name).toBe('Read Only');
57
- });
58
- it('returns a tolerant error listing available roles on a miss', () => {
59
- const r = resolveRoleByIDOrName('nope', roles);
60
- expect(r.ok).toBe(false);
61
- if (!r.ok) {
62
- expect(r.error).toContain('Administrator');
63
- }
64
- });
65
- it('errors on empty input', () => {
66
- expect(resolveRoleByIDOrName(' ', roles).ok).toBe(false);
67
- });
68
- });
69
- describe('buildRoleManagementAgentContext', () => {
70
- it('emits the core counts and filter state', () => {
71
- const ctx = buildRoleManagementAgentContext(baseInput());
72
- expect(ctx['TotalRoleCount']).toBe(4);
73
- expect(ctx['SystemRoleCount']).toBe(2);
74
- expect(ctx['CustomRoleCount']).toBe(2);
75
- expect(ctx['TypeFilter']).toBe('all');
76
- expect(ctx['VisibleRoleNames']).toEqual(['Administrator', 'Developer']);
77
- });
78
- it('omits the permission summary when no role is selected', () => {
79
- const ctx = buildRoleManagementAgentContext(baseInput());
80
- expect(ctx['SelectedRolePermissions']).toBeUndefined();
81
- });
82
- it('includes the read-only permission summary when a role is selected', () => {
83
- const input = baseInput();
84
- input.SelectedRoleId = 'R1';
85
- input.SelectedRoleName = 'Administrator';
86
- input.SelectedRolePermissions = summary;
87
- const ctx = buildRoleManagementAgentContext(input);
88
- expect(ctx['SelectedRoleName']).toBe('Administrator');
89
- expect(ctx['SelectedRolePermissions']).toEqual(summary);
90
- });
91
- it('caps a long role-name list and emits a companion count', () => {
92
- const input = baseInput();
93
- input.VisibleRoleNames = Array.from({ length: ROLE_AGENT_CONTEXT_NAME_LIST_CAP + 4 }, (_, i) => `Role ${i}`);
94
- const ctx = buildRoleManagementAgentContext(input);
95
- expect(ctx['VisibleRoleNames'].length).toBe(ROLE_AGENT_CONTEXT_NAME_LIST_CAP);
96
- expect(ctx['VisibleRoleCount']).toBe(ROLE_AGENT_CONTEXT_NAME_LIST_CAP + 4);
97
- });
98
- it('the permission summary is counts-only (no grant control fields)', () => {
99
- const input = baseInput();
100
- input.SelectedRolePermissions = summary;
101
- input.SelectedRoleId = 'R1';
102
- const ctx = buildRoleManagementAgentContext(input);
103
- const perms = ctx['SelectedRolePermissions'];
104
- expect(Object.keys(perms).sort()).toEqual(['CreateCount', 'DeleteCount', 'EntityCount', 'ReadCount', 'UpdateCount']);
105
- });
106
- });
107
- //# sourceMappingURL=role-management-agent-context.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"role-management-agent-context.test.js","sourceRoot":"","sources":["../../src/__tests__/role-management-agent-context.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect } from 'vitest';\nimport {\n buildRoleManagementAgentContext,\n isValidRoleTypeFilter,\n capRoleNames,\n resolveRoleByIDOrName,\n ROLE_AGENT_CONTEXT_NAME_LIST_CAP,\n RoleManagementAgentContextInput,\n RoleNameCandidate,\n RolePermissionSummary,\n} from '../lib/role-management/role-management-agent-context';\n\nconst baseInput = (): RoleManagementAgentContextInput => ({\n TotalRoleCount: 4,\n FilteredRoleCount: 2,\n SystemRoleCount: 2,\n CustomRoleCount: 2,\n TypeFilter: 'all',\n SearchText: '',\n SelectedRoleId: null,\n SelectedRoleName: null,\n VisibleRoleNames: ['Administrator', 'Developer'],\n SelectedRolePermissions: null,\n});\n\nconst roles: RoleNameCandidate[] = [\n { ID: 'R1', Name: 'Administrator' },\n { ID: 'R2', Name: 'Developer' },\n { ID: 'R3', Name: 'Read Only' },\n];\n\nconst summary: RolePermissionSummary = {\n EntityCount: 10,\n ReadCount: 10,\n CreateCount: 3,\n UpdateCount: 3,\n DeleteCount: 1,\n};\n\ndescribe('isValidRoleTypeFilter', () => {\n it('accepts the three valid filters', () => {\n expect(isValidRoleTypeFilter('all')).toBe(true);\n expect(isValidRoleTypeFilter('system')).toBe(true);\n expect(isValidRoleTypeFilter('custom')).toBe(true);\n });\n it('rejects anything else', () => {\n expect(isValidRoleTypeFilter('System')).toBe(false);\n expect(isValidRoleTypeFilter('')).toBe(false);\n expect(isValidRoleTypeFilter(7)).toBe(false);\n });\n});\n\ndescribe('capRoleNames', () => {\n it('caps at the configured limit', () => {\n const many = Array.from({ length: ROLE_AGENT_CONTEXT_NAME_LIST_CAP + 3 }, (_, i) => `r${i}`);\n expect(capRoleNames(many)).toHaveLength(ROLE_AGENT_CONTEXT_NAME_LIST_CAP);\n });\n});\n\ndescribe('resolveRoleByIDOrName', () => {\n it('matches by exact ID (case-insensitive)', () => {\n const r = resolveRoleByIDOrName('r1', roles);\n expect(r.ok && r.match.Name).toBe('Administrator');\n });\n it('matches by exact name', () => {\n const r = resolveRoleByIDOrName('Developer', roles);\n expect(r.ok && r.match.ID).toBe('R2');\n });\n it('falls back to contains', () => {\n const r = resolveRoleByIDOrName('read', roles);\n expect(r.ok && r.match.Name).toBe('Read Only');\n });\n it('returns a tolerant error listing available roles on a miss', () => {\n const r = resolveRoleByIDOrName('nope', roles);\n expect(r.ok).toBe(false);\n if (!r.ok) {\n expect(r.error).toContain('Administrator');\n }\n });\n it('errors on empty input', () => {\n expect(resolveRoleByIDOrName(' ', roles).ok).toBe(false);\n });\n});\n\ndescribe('buildRoleManagementAgentContext', () => {\n it('emits the core counts and filter state', () => {\n const ctx = buildRoleManagementAgentContext(baseInput());\n expect(ctx['TotalRoleCount']).toBe(4);\n expect(ctx['SystemRoleCount']).toBe(2);\n expect(ctx['CustomRoleCount']).toBe(2);\n expect(ctx['TypeFilter']).toBe('all');\n expect(ctx['VisibleRoleNames']).toEqual(['Administrator', 'Developer']);\n });\n it('omits the permission summary when no role is selected', () => {\n const ctx = buildRoleManagementAgentContext(baseInput());\n expect(ctx['SelectedRolePermissions']).toBeUndefined();\n });\n it('includes the read-only permission summary when a role is selected', () => {\n const input = baseInput();\n input.SelectedRoleId = 'R1';\n input.SelectedRoleName = 'Administrator';\n input.SelectedRolePermissions = summary;\n const ctx = buildRoleManagementAgentContext(input);\n expect(ctx['SelectedRoleName']).toBe('Administrator');\n expect(ctx['SelectedRolePermissions']).toEqual(summary);\n });\n it('caps a long role-name list and emits a companion count', () => {\n const input = baseInput();\n input.VisibleRoleNames = Array.from({ length: ROLE_AGENT_CONTEXT_NAME_LIST_CAP + 4 }, (_, i) => `Role ${i}`);\n const ctx = buildRoleManagementAgentContext(input);\n expect((ctx['VisibleRoleNames'] as string[]).length).toBe(ROLE_AGENT_CONTEXT_NAME_LIST_CAP);\n expect(ctx['VisibleRoleCount']).toBe(ROLE_AGENT_CONTEXT_NAME_LIST_CAP + 4);\n });\n it('the permission summary is counts-only (no grant control fields)', () => {\n const input = baseInput();\n input.SelectedRolePermissions = summary;\n input.SelectedRoleId = 'R1';\n const ctx = buildRoleManagementAgentContext(input);\n const perms = ctx['SelectedRolePermissions'] as Record<string, unknown>;\n expect(Object.keys(perms).sort()).toEqual(['CreateCount', 'DeleteCount', 'EntityCount', 'ReadCount', 'UpdateCount']);\n });\n});\n"]}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=user-app-config-reorder.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"user-app-config-reorder.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/user-app-config-reorder.test.ts"],"names":[],"mappings":""}
@@ -1,66 +0,0 @@
1
- /**
2
- * Covers the app-config dialog's positional reorder/resequence helpers.
3
- *
4
- * Regression for issue #3027: rows can carry DUPLICATE Sequence values (e.g. a
5
- * re-enabled app kept a stale value), and the old value-swap MoveUp/MoveDown swapped
6
- * two equal numbers — rows went dirty, Save enabled, and the order silently never
7
- * changed. Positional moves must reorder regardless of the stored values and heal
8
- * duplicates by renumbering 0..n-1.
9
- */
10
- import { describe, it, expect } from 'vitest';
11
- import { moveAndResequence, resequenceItems } from '../lib/user-app-config/user-app-config-reorder';
12
- function item(name, sequence) {
13
- return { name, sequence, isDirty: false };
14
- }
15
- const names = (items) => items.map(i => i.name).join(',');
16
- const seqs = (items) => items.map(i => i.sequence).join(',');
17
- describe('moveAndResequence', () => {
18
- it('reorders and renumbers with clean sequential values', () => {
19
- const result = moveAndResequence([item('a', 0), item('b', 1), item('c', 2)], 2, 1);
20
- expect(names(result)).toBe('a,c,b');
21
- expect(seqs(result)).toBe('0,1,2');
22
- });
23
- it('reorders even when the two neighbors hold DUPLICATE sequence values (the #3027 no-op case)', () => {
24
- // Old value-swap behavior: swapping 6 and 6 changed nothing — order stuck forever.
25
- const result = moveAndResequence([item('a', 0), item('bulk', 6), item('testing', 6)], 2, 1);
26
- expect(names(result)).toBe('a,testing,bulk');
27
- expect(seqs(result)).toBe('0,1,2'); // duplicates healed
28
- expect(result.every(i => i.sequence !== 6 || i.isDirty)).toBe(true);
29
- });
30
- it('reorders across a stale 999 park value and heals it', () => {
31
- const result = moveAndResequence([item('a', 0), item('b', 1), item('reenabled', 999)], 2, 0);
32
- expect(names(result)).toBe('reenabled,a,b');
33
- expect(seqs(result)).toBe('0,1,2');
34
- });
35
- it('marks exactly the renumbered items dirty', () => {
36
- const result = moveAndResequence([item('a', 0), item('b', 1), item('c', 2)], 2, 1);
37
- expect(result.map(i => i.isDirty)).toEqual([false, true, true]); // a keeps 0, c→1 and b→2 changed
38
- });
39
- it('returns the input unchanged for out-of-range or no-op moves', () => {
40
- const input = [item('a', 5), item('b', 5)];
41
- expect(moveAndResequence(input, -1, 0)).toBe(input);
42
- expect(moveAndResequence(input, 0, 2)).toBe(input);
43
- expect(moveAndResequence(input, 1, 1)).toBe(input);
44
- expect(seqs(input)).toBe('5,5'); // untouched — no surprise renumbering on a no-op
45
- });
46
- it('does not mutate the input array order (returns a new array)', () => {
47
- const input = [item('a', 0), item('b', 1)];
48
- const result = moveAndResequence(input, 1, 0);
49
- expect(names(input)).toBe('a,b');
50
- expect(result).not.toBe(input);
51
- });
52
- });
53
- describe('resequenceItems', () => {
54
- it('renumbers duplicates in display order and dirties only corrected items', () => {
55
- const items = [item('a', 0), item('b', 6), item('c', 6), item('d', 999)];
56
- resequenceItems(items);
57
- expect(seqs(items)).toBe('0,1,2,3');
58
- expect(items.map(i => i.isDirty)).toEqual([false, true, true, true]);
59
- });
60
- it('is a no-op on an already-sequential list', () => {
61
- const items = [item('a', 0), item('b', 1)];
62
- resequenceItems(items);
63
- expect(items.every(i => !i.isDirty)).toBe(true);
64
- });
65
- });
66
- //# sourceMappingURL=user-app-config-reorder.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"user-app-config-reorder.test.js","sourceRoot":"","sources":["../../src/__tests__/user-app-config-reorder.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Covers the app-config dialog's positional reorder/resequence helpers.\n *\n * Regression for issue #3027: rows can carry DUPLICATE Sequence values (e.g. a\n * re-enabled app kept a stale value), and the old value-swap MoveUp/MoveDown swapped\n * two equal numbers — rows went dirty, Save enabled, and the order silently never\n * changed. Positional moves must reorder regardless of the stored values and heal\n * duplicates by renumbering 0..n-1.\n */\nimport { describe, it, expect } from 'vitest';\nimport { moveAndResequence, resequenceItems, ReorderableAppItem } from '../lib/user-app-config/user-app-config-reorder';\n\ninterface TestItem extends ReorderableAppItem {\n name: string;\n}\n\nfunction item(name: string, sequence: number): TestItem {\n return { name, sequence, isDirty: false };\n}\n\nconst names = (items: TestItem[]) => items.map(i => i.name).join(',');\nconst seqs = (items: TestItem[]) => items.map(i => i.sequence).join(',');\n\ndescribe('moveAndResequence', () => {\n it('reorders and renumbers with clean sequential values', () => {\n const result = moveAndResequence([item('a', 0), item('b', 1), item('c', 2)], 2, 1);\n expect(names(result)).toBe('a,c,b');\n expect(seqs(result)).toBe('0,1,2');\n });\n\n it('reorders even when the two neighbors hold DUPLICATE sequence values (the #3027 no-op case)', () => {\n // Old value-swap behavior: swapping 6 and 6 changed nothing — order stuck forever.\n const result = moveAndResequence([item('a', 0), item('bulk', 6), item('testing', 6)], 2, 1);\n expect(names(result)).toBe('a,testing,bulk');\n expect(seqs(result)).toBe('0,1,2'); // duplicates healed\n expect(result.every(i => i.sequence !== 6 || i.isDirty)).toBe(true);\n });\n\n it('reorders across a stale 999 park value and heals it', () => {\n const result = moveAndResequence([item('a', 0), item('b', 1), item('reenabled', 999)], 2, 0);\n expect(names(result)).toBe('reenabled,a,b');\n expect(seqs(result)).toBe('0,1,2');\n });\n\n it('marks exactly the renumbered items dirty', () => {\n const result = moveAndResequence([item('a', 0), item('b', 1), item('c', 2)], 2, 1);\n expect(result.map(i => i.isDirty)).toEqual([false, true, true]); // a keeps 0, c→1 and b→2 changed\n });\n\n it('returns the input unchanged for out-of-range or no-op moves', () => {\n const input = [item('a', 5), item('b', 5)];\n expect(moveAndResequence(input, -1, 0)).toBe(input);\n expect(moveAndResequence(input, 0, 2)).toBe(input);\n expect(moveAndResequence(input, 1, 1)).toBe(input);\n expect(seqs(input)).toBe('5,5'); // untouched — no surprise renumbering on a no-op\n });\n\n it('does not mutate the input array order (returns a new array)', () => {\n const input = [item('a', 0), item('b', 1)];\n const result = moveAndResequence(input, 1, 0);\n expect(names(input)).toBe('a,b');\n expect(result).not.toBe(input);\n });\n});\n\ndescribe('resequenceItems', () => {\n it('renumbers duplicates in display order and dirties only corrected items', () => {\n const items = [item('a', 0), item('b', 6), item('c', 6), item('d', 999)];\n resequenceItems(items);\n expect(seqs(items)).toBe('0,1,2,3');\n expect(items.map(i => i.isDirty)).toEqual([false, true, true, true]);\n });\n\n it('is a no-op on an already-sequential list', () => {\n const items = [item('a', 0), item('b', 1)];\n resequenceItems(items);\n expect(items.every(i => !i.isDirty)).toBe(true);\n });\n});\n"]}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=user-management-agent-context.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"user-management-agent-context.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/user-management-agent-context.test.ts"],"names":[],"mappings":""}
@@ -1,134 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { buildUserManagementAgentContext, isValidUserStatusFilter, capUserNames, resolveUserByIDOrName, resolveRoleByIDOrName, USER_AGENT_CONTEXT_NAME_LIST_CAP, } from '../lib/user-management/user-management-agent-context';
3
- const baseInput = () => ({
4
- TotalUserCount: 3,
5
- FilteredUserCount: 2,
6
- ActiveUserCount: 2,
7
- InactiveUserCount: 1,
8
- StatusFilter: 'active',
9
- RoleFilterId: null,
10
- RoleFilterName: null,
11
- SearchText: '',
12
- SelectedUserId: null,
13
- SelectedUserName: null,
14
- VisibleUserNames: ['Alice', 'Bob'],
15
- VisibleUserEmails: ['alice@x.com', 'bob@x.com'],
16
- AvailableRoleNames: ['Administrator', 'User'],
17
- VisibleColumns: ['Name', 'Email'],
18
- });
19
- const users = [
20
- { ID: 'AAAA1111-0000-0000-0000-000000000001', Name: 'Alice Smith', Email: 'alice@x.com', FirstName: 'Alice', LastName: 'Smith' },
21
- { ID: 'BBBB2222-0000-0000-0000-000000000002', Name: 'Bob Jones', Email: 'bob@x.com', FirstName: 'Bob', LastName: 'Jones' },
22
- ];
23
- const roles = [
24
- { ID: 'R1', Name: 'Administrator' },
25
- { ID: 'R2', Name: 'Developer' },
26
- ];
27
- describe('isValidUserStatusFilter', () => {
28
- it('accepts the three valid filters', () => {
29
- expect(isValidUserStatusFilter('all')).toBe(true);
30
- expect(isValidUserStatusFilter('active')).toBe(true);
31
- expect(isValidUserStatusFilter('inactive')).toBe(true);
32
- });
33
- it('rejects anything else', () => {
34
- expect(isValidUserStatusFilter('Active')).toBe(false);
35
- expect(isValidUserStatusFilter('')).toBe(false);
36
- expect(isValidUserStatusFilter(42)).toBe(false);
37
- expect(isValidUserStatusFilter(null)).toBe(false);
38
- });
39
- });
40
- describe('capUserNames', () => {
41
- it('caps at the configured limit', () => {
42
- const many = Array.from({ length: USER_AGENT_CONTEXT_NAME_LIST_CAP + 10 }, (_, i) => `u${i}`);
43
- expect(capUserNames(many)).toHaveLength(USER_AGENT_CONTEXT_NAME_LIST_CAP);
44
- });
45
- it('leaves short lists intact', () => {
46
- expect(capUserNames(['a', 'b'])).toEqual(['a', 'b']);
47
- });
48
- });
49
- describe('resolveUserByIDOrName', () => {
50
- it('matches by exact ID (case-insensitive)', () => {
51
- const r = resolveUserByIDOrName('aaaa1111-0000-0000-0000-000000000001', users);
52
- expect(r.ok && r.match.Name).toBe('Alice Smith');
53
- });
54
- it('matches by exact display name', () => {
55
- const r = resolveUserByIDOrName('Bob Jones', users);
56
- expect(r.ok && r.match.ID).toBe('BBBB2222-0000-0000-0000-000000000002');
57
- });
58
- it('matches by exact email', () => {
59
- const r = resolveUserByIDOrName('alice@x.com', users);
60
- expect(r.ok && r.match.Name).toBe('Alice Smith');
61
- });
62
- it('matches by "First Last" combination', () => {
63
- const r = resolveUserByIDOrName('alice smith', users);
64
- expect(r.ok && r.match.ID).toBe(users[0].ID);
65
- });
66
- it('falls back to a contains match', () => {
67
- const r = resolveUserByIDOrName('bob', users);
68
- expect(r.ok && r.match.Name).toBe('Bob Jones');
69
- });
70
- it('returns a tolerant error with a sample on a miss', () => {
71
- const r = resolveUserByIDOrName('nobody', users);
72
- expect(r.ok).toBe(false);
73
- if (!r.ok) {
74
- expect(r.error).toContain('Alice Smith');
75
- }
76
- });
77
- it('errors on empty input', () => {
78
- expect(resolveUserByIDOrName(' ', users).ok).toBe(false);
79
- });
80
- });
81
- describe('resolveRoleByIDOrName', () => {
82
- it('matches by id then name then contains', () => {
83
- expect(resolveRoleByIDOrName('R2', roles).match.Name).toBe('Developer');
84
- expect(resolveRoleByIDOrName('administrator', roles).match.ID).toBe('R1');
85
- expect(resolveRoleByIDOrName('dev', roles).match.Name).toBe('Developer');
86
- });
87
- it('errors tolerantly on a miss', () => {
88
- const r = resolveRoleByIDOrName('zzz', roles);
89
- expect(r.ok).toBe(false);
90
- });
91
- });
92
- describe('buildUserManagementAgentContext', () => {
93
- it('emits the core counts and filter state', () => {
94
- const ctx = buildUserManagementAgentContext(baseInput());
95
- expect(ctx['TotalUserCount']).toBe(3);
96
- expect(ctx['FilteredUserCount']).toBe(2);
97
- expect(ctx['StatusFilter']).toBe('active');
98
- expect(ctx['VisibleUserNames']).toEqual(['Alice', 'Bob']);
99
- expect(ctx['AvailableRoleNames']).toEqual(['Administrator', 'User']);
100
- });
101
- it('omits empty name lists (no fabricated empties)', () => {
102
- const input = baseInput();
103
- input.VisibleUserNames = [];
104
- input.VisibleUserEmails = [];
105
- const ctx = buildUserManagementAgentContext(input);
106
- expect(ctx['VisibleUserNames']).toBeUndefined();
107
- expect(ctx['VisibleUserEmails']).toBeUndefined();
108
- });
109
- it('caps long lists and emits a companion count', () => {
110
- const input = baseInput();
111
- input.VisibleUserNames = Array.from({ length: USER_AGENT_CONTEXT_NAME_LIST_CAP + 5 }, (_, i) => `User ${i}`);
112
- const ctx = buildUserManagementAgentContext(input);
113
- expect(ctx['VisibleUserNames'].length).toBe(USER_AGENT_CONTEXT_NAME_LIST_CAP);
114
- expect(ctx['VisibleUserNameCount']).toBe(USER_AGENT_CONTEXT_NAME_LIST_CAP + 5);
115
- });
116
- it('carries the selected user name (display only) and role filter name', () => {
117
- const input = baseInput();
118
- input.SelectedUserId = users[0].ID;
119
- input.SelectedUserName = 'Alice Smith';
120
- input.RoleFilterId = 'R1';
121
- input.RoleFilterName = 'Administrator';
122
- const ctx = buildUserManagementAgentContext(input);
123
- expect(ctx['SelectedUserName']).toBe('Alice Smith');
124
- expect(ctx['RoleFilterName']).toBe('Administrator');
125
- });
126
- it('never leaks a password/token field', () => {
127
- const ctx = buildUserManagementAgentContext(baseInput());
128
- const keys = Object.keys(ctx).join(' ').toLowerCase();
129
- expect(keys).not.toContain('password');
130
- expect(keys).not.toContain('token');
131
- expect(keys).not.toContain('secret');
132
- });
133
- });
134
- //# sourceMappingURL=user-management-agent-context.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"user-management-agent-context.test.js","sourceRoot":"","sources":["../../src/__tests__/user-management-agent-context.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect } from 'vitest';\nimport {\n buildUserManagementAgentContext,\n isValidUserStatusFilter,\n capUserNames,\n resolveUserByIDOrName,\n resolveRoleByIDOrName,\n USER_AGENT_CONTEXT_NAME_LIST_CAP,\n UserManagementAgentContextInput,\n UserNameCandidate,\n RoleNameCandidate,\n} from '../lib/user-management/user-management-agent-context';\n\nconst baseInput = (): UserManagementAgentContextInput => ({\n TotalUserCount: 3,\n FilteredUserCount: 2,\n ActiveUserCount: 2,\n InactiveUserCount: 1,\n StatusFilter: 'active',\n RoleFilterId: null,\n RoleFilterName: null,\n SearchText: '',\n SelectedUserId: null,\n SelectedUserName: null,\n VisibleUserNames: ['Alice', 'Bob'],\n VisibleUserEmails: ['alice@x.com', 'bob@x.com'],\n AvailableRoleNames: ['Administrator', 'User'],\n VisibleColumns: ['Name', 'Email'],\n});\n\nconst users: UserNameCandidate[] = [\n { ID: 'AAAA1111-0000-0000-0000-000000000001', Name: 'Alice Smith', Email: 'alice@x.com', FirstName: 'Alice', LastName: 'Smith' },\n { ID: 'BBBB2222-0000-0000-0000-000000000002', Name: 'Bob Jones', Email: 'bob@x.com', FirstName: 'Bob', LastName: 'Jones' },\n];\n\nconst roles: RoleNameCandidate[] = [\n { ID: 'R1', Name: 'Administrator' },\n { ID: 'R2', Name: 'Developer' },\n];\n\ndescribe('isValidUserStatusFilter', () => {\n it('accepts the three valid filters', () => {\n expect(isValidUserStatusFilter('all')).toBe(true);\n expect(isValidUserStatusFilter('active')).toBe(true);\n expect(isValidUserStatusFilter('inactive')).toBe(true);\n });\n it('rejects anything else', () => {\n expect(isValidUserStatusFilter('Active')).toBe(false);\n expect(isValidUserStatusFilter('')).toBe(false);\n expect(isValidUserStatusFilter(42)).toBe(false);\n expect(isValidUserStatusFilter(null)).toBe(false);\n });\n});\n\ndescribe('capUserNames', () => {\n it('caps at the configured limit', () => {\n const many = Array.from({ length: USER_AGENT_CONTEXT_NAME_LIST_CAP + 10 }, (_, i) => `u${i}`);\n expect(capUserNames(many)).toHaveLength(USER_AGENT_CONTEXT_NAME_LIST_CAP);\n });\n it('leaves short lists intact', () => {\n expect(capUserNames(['a', 'b'])).toEqual(['a', 'b']);\n });\n});\n\ndescribe('resolveUserByIDOrName', () => {\n it('matches by exact ID (case-insensitive)', () => {\n const r = resolveUserByIDOrName('aaaa1111-0000-0000-0000-000000000001', users);\n expect(r.ok && r.match.Name).toBe('Alice Smith');\n });\n it('matches by exact display name', () => {\n const r = resolveUserByIDOrName('Bob Jones', users);\n expect(r.ok && r.match.ID).toBe('BBBB2222-0000-0000-0000-000000000002');\n });\n it('matches by exact email', () => {\n const r = resolveUserByIDOrName('alice@x.com', users);\n expect(r.ok && r.match.Name).toBe('Alice Smith');\n });\n it('matches by \"First Last\" combination', () => {\n const r = resolveUserByIDOrName('alice smith', users);\n expect(r.ok && r.match.ID).toBe(users[0].ID);\n });\n it('falls back to a contains match', () => {\n const r = resolveUserByIDOrName('bob', users);\n expect(r.ok && r.match.Name).toBe('Bob Jones');\n });\n it('returns a tolerant error with a sample on a miss', () => {\n const r = resolveUserByIDOrName('nobody', users);\n expect(r.ok).toBe(false);\n if (!r.ok) {\n expect(r.error).toContain('Alice Smith');\n }\n });\n it('errors on empty input', () => {\n expect(resolveUserByIDOrName(' ', users).ok).toBe(false);\n });\n});\n\ndescribe('resolveRoleByIDOrName', () => {\n it('matches by id then name then contains', () => {\n expect((resolveRoleByIDOrName('R2', roles) as { ok: true; match: RoleNameCandidate }).match.Name).toBe('Developer');\n expect((resolveRoleByIDOrName('administrator', roles) as { ok: true; match: RoleNameCandidate }).match.ID).toBe('R1');\n expect((resolveRoleByIDOrName('dev', roles) as { ok: true; match: RoleNameCandidate }).match.Name).toBe('Developer');\n });\n it('errors tolerantly on a miss', () => {\n const r = resolveRoleByIDOrName('zzz', roles);\n expect(r.ok).toBe(false);\n });\n});\n\ndescribe('buildUserManagementAgentContext', () => {\n it('emits the core counts and filter state', () => {\n const ctx = buildUserManagementAgentContext(baseInput());\n expect(ctx['TotalUserCount']).toBe(3);\n expect(ctx['FilteredUserCount']).toBe(2);\n expect(ctx['StatusFilter']).toBe('active');\n expect(ctx['VisibleUserNames']).toEqual(['Alice', 'Bob']);\n expect(ctx['AvailableRoleNames']).toEqual(['Administrator', 'User']);\n });\n it('omits empty name lists (no fabricated empties)', () => {\n const input = baseInput();\n input.VisibleUserNames = [];\n input.VisibleUserEmails = [];\n const ctx = buildUserManagementAgentContext(input);\n expect(ctx['VisibleUserNames']).toBeUndefined();\n expect(ctx['VisibleUserEmails']).toBeUndefined();\n });\n it('caps long lists and emits a companion count', () => {\n const input = baseInput();\n input.VisibleUserNames = Array.from({ length: USER_AGENT_CONTEXT_NAME_LIST_CAP + 5 }, (_, i) => `User ${i}`);\n const ctx = buildUserManagementAgentContext(input);\n expect((ctx['VisibleUserNames'] as string[]).length).toBe(USER_AGENT_CONTEXT_NAME_LIST_CAP);\n expect(ctx['VisibleUserNameCount']).toBe(USER_AGENT_CONTEXT_NAME_LIST_CAP + 5);\n });\n it('carries the selected user name (display only) and role filter name', () => {\n const input = baseInput();\n input.SelectedUserId = users[0].ID;\n input.SelectedUserName = 'Alice Smith';\n input.RoleFilterId = 'R1';\n input.RoleFilterName = 'Administrator';\n const ctx = buildUserManagementAgentContext(input);\n expect(ctx['SelectedUserName']).toBe('Alice Smith');\n expect(ctx['RoleFilterName']).toBe('Administrator');\n });\n it('never leaks a password/token field', () => {\n const ctx = buildUserManagementAgentContext(baseInput());\n const keys = Object.keys(ctx).join(' ').toLowerCase();\n expect(keys).not.toContain('password');\n expect(keys).not.toContain('token');\n expect(keys).not.toContain('secret');\n });\n});\n"]}