@memberjunction/ng-list-management 5.43.0 → 5.45.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/dist/lib/components/list-delta-confirm/list-delta-confirm.component.js +32 -36
- package/dist/lib/components/list-delta-confirm/list-delta-confirm.component.js.map +1 -1
- package/dist/lib/components/list-management-dialog/list-management-dialog.component.js +73 -94
- package/dist/lib/components/list-management-dialog/list-management-dialog.component.js.map +1 -1
- package/dist/lib/components/list-share-dialog/list-share-dialog.component.d.ts +5 -0
- package/dist/lib/components/list-share-dialog/list-share-dialog.component.d.ts.map +1 -1
- package/dist/lib/components/list-share-dialog/list-share-dialog.component.js +20 -22
- package/dist/lib/components/list-share-dialog/list-share-dialog.component.js.map +1 -1
- package/dist/lib/components/save-view-as-list-dialog/save-view-as-list-dialog.component.js +72 -74
- package/dist/lib/components/save-view-as-list-dialog/save-view-as-list-dialog.component.js.map +1 -1
- package/dist/lib/module.d.ts +1 -1
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/module.js +7 -1
- package/dist/lib/module.js.map +1 -1
- package/package.json +12 -11
- package/dist/__tests__/index.test.d.ts +0 -2
- package/dist/__tests__/index.test.d.ts.map +0 -1
- package/dist/__tests__/index.test.js +0 -7
- package/dist/__tests__/index.test.js.map +0 -1
- package/dist/__tests__/list-models.test.d.ts +0 -2
- package/dist/__tests__/list-models.test.d.ts.map +0 -1
- package/dist/__tests__/list-models.test.js +0 -86
- package/dist/__tests__/list-models.test.js.map +0 -1
- package/dist/lib/components/list-invitations/__tests__/list-invitations.component.test.d.ts +0 -2
- package/dist/lib/components/list-invitations/__tests__/list-invitations.component.test.d.ts.map +0 -1
- package/dist/lib/components/list-invitations/__tests__/list-invitations.component.test.js +0 -145
- package/dist/lib/components/list-invitations/__tests__/list-invitations.component.test.js.map +0 -1
package/dist/lib/module.js
CHANGED
|
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
|
|
|
2
2
|
import { CommonModule } from '@angular/common';
|
|
3
3
|
import { FormsModule } from '@angular/forms';
|
|
4
4
|
// MJ UI Components
|
|
5
|
-
import { MJButtonDirective, MJDialogComponent, MJDialogActionsComponent, MJDropdownComponent } from '@memberjunction/ng-ui-components';
|
|
5
|
+
import { MJButtonDirective, MJDialogComponent, MJDialogActionsComponent, MJDropdownComponent, MJEmptyStateComponent, MJAlertComponent } from '@memberjunction/ng-ui-components';
|
|
6
6
|
// MemberJunction imports
|
|
7
7
|
import { ContainerDirectivesModule } from '@memberjunction/ng-container-directives';
|
|
8
8
|
import { SharedGenericModule } from '@memberjunction/ng-shared-generic';
|
|
@@ -36,6 +36,8 @@ export class ListManagementModule {
|
|
|
36
36
|
MJDialogComponent,
|
|
37
37
|
MJDialogActionsComponent,
|
|
38
38
|
MJDropdownComponent,
|
|
39
|
+
MJEmptyStateComponent,
|
|
40
|
+
MJAlertComponent,
|
|
39
41
|
ContainerDirectivesModule,
|
|
40
42
|
SharedGenericModule] });
|
|
41
43
|
}
|
|
@@ -62,6 +64,8 @@ export class ListManagementModule {
|
|
|
62
64
|
MJDialogComponent,
|
|
63
65
|
MJDialogActionsComponent,
|
|
64
66
|
MJDropdownComponent,
|
|
67
|
+
MJEmptyStateComponent,
|
|
68
|
+
MJAlertComponent,
|
|
65
69
|
ContainerDirectivesModule,
|
|
66
70
|
SharedGenericModule
|
|
67
71
|
],
|
|
@@ -100,6 +104,8 @@ export class ListManagementModule {
|
|
|
100
104
|
MJDialogComponent,
|
|
101
105
|
MJDialogActionsComponent,
|
|
102
106
|
MJDropdownComponent,
|
|
107
|
+
MJEmptyStateComponent,
|
|
108
|
+
MJAlertComponent,
|
|
103
109
|
ContainerDirectivesModule,
|
|
104
110
|
SharedGenericModule], exports: [AudienceSourcePickerComponent,
|
|
105
111
|
AudienceSourceSummaryComponent,
|
package/dist/lib/module.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/lib/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,mBAAmB;AACnB,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,wBAAwB,EACxB,mBAAmB,
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/lib/module.ts"],"names":[],"mappings":"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","sourcesContent":["import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\n\n// MJ UI Components\nimport {\n MJButtonDirective,\n MJDialogComponent,\n MJDialogActionsComponent,\n MJDropdownComponent,\n MJEmptyStateComponent,\n MJAlertComponent\n} from '@memberjunction/ng-ui-components';\n\n// MemberJunction imports\nimport { ContainerDirectivesModule } from '@memberjunction/ng-container-directives';\nimport { SharedGenericModule } from '@memberjunction/ng-shared-generic';\n\n// Components\nimport { AudienceSourcePickerComponent } from './components/audience-source-picker/audience-source-picker.component';\nimport { AudienceSourceSummaryComponent } from './components/audience-source-summary/audience-source-summary.component';\nimport { ListAuditLogComponent } from './components/list-audit-log/list-audit-log.component';\nimport { ListDeltaConfirmComponent } from './components/list-delta-confirm/list-delta-confirm.component';\nimport { ListInvitationsComponent } from './components/list-invitations/list-invitations.component';\nimport { ListManagementDialogComponent } from './components/list-management-dialog/list-management-dialog.component';\nimport { ListShareDialogComponent } from './components/list-share-dialog/list-share-dialog.component';\nimport { ListStatsComponent } from './components/list-stats/list-stats.component';\nimport { ListsSharedWithMeComponent } from './components/shared-with-me/shared-with-me.component';\nimport { SaveViewAsListDialogComponent } from './components/save-view-as-list-dialog/save-view-as-list-dialog.component';\nimport { TagChipsComponent } from './components/tag-chips/tag-chips.component';\n\n// Services\nimport { ListManagementService } from './services/list-management.service';\nimport { ListSharingService } from './services/list-sharing.service';\n\n/**\n * Module providing list management components for MemberJunction.\n */\n@NgModule({\n declarations: [\n AudienceSourcePickerComponent,\n AudienceSourceSummaryComponent,\n ListAuditLogComponent,\n ListDeltaConfirmComponent,\n ListInvitationsComponent,\n ListManagementDialogComponent,\n ListShareDialogComponent,\n ListStatsComponent,\n ListsSharedWithMeComponent,\n SaveViewAsListDialogComponent,\n TagChipsComponent\n ],\n imports: [\n CommonModule,\n FormsModule,\n MJButtonDirective,\n MJDialogComponent,\n MJDialogActionsComponent,\n MJDropdownComponent,\n MJEmptyStateComponent,\n MJAlertComponent,\n ContainerDirectivesModule,\n SharedGenericModule\n ],\n exports: [\n AudienceSourcePickerComponent,\n AudienceSourceSummaryComponent,\n ListAuditLogComponent,\n ListDeltaConfirmComponent,\n ListInvitationsComponent,\n ListManagementDialogComponent,\n ListShareDialogComponent,\n ListStatsComponent,\n ListsSharedWithMeComponent,\n SaveViewAsListDialogComponent,\n TagChipsComponent\n ],\n providers: [\n ListManagementService,\n ListSharingService\n ]\n})\nexport class ListManagementModule { }\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-list-management",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.45.0",
|
|
4
4
|
"description": "MemberJunction: Angular Components for managing entity list membership with beautiful, responsive UI",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@angular/compiler": "21.1.3",
|
|
25
25
|
"@angular/compiler-cli": "21.1.3",
|
|
26
|
+
"@memberjunction/ng-test-utils": "5.45.0",
|
|
26
27
|
"vitest": "^4.0.18"
|
|
27
28
|
},
|
|
28
29
|
"peerDependencies": {
|
|
@@ -33,16 +34,16 @@
|
|
|
33
34
|
"rxjs": "^7.8.2"
|
|
34
35
|
},
|
|
35
36
|
"dependencies": {
|
|
36
|
-
"@memberjunction/core": "5.
|
|
37
|
-
"@memberjunction/core-entities": "5.
|
|
38
|
-
"@memberjunction/global": "5.
|
|
39
|
-
"@memberjunction/graphql-dataprovider": "5.
|
|
40
|
-
"@memberjunction/lists-base": "5.
|
|
41
|
-
"@memberjunction/ng-base-types": "5.
|
|
42
|
-
"@memberjunction/ng-container-directives": "5.
|
|
43
|
-
"@memberjunction/ng-shared": "5.
|
|
44
|
-
"@memberjunction/ng-shared-generic": "5.
|
|
45
|
-
"@memberjunction/ng-ui-components": "5.
|
|
37
|
+
"@memberjunction/core": "5.45.0",
|
|
38
|
+
"@memberjunction/core-entities": "5.45.0",
|
|
39
|
+
"@memberjunction/global": "5.45.0",
|
|
40
|
+
"@memberjunction/graphql-dataprovider": "5.45.0",
|
|
41
|
+
"@memberjunction/lists-base": "5.45.0",
|
|
42
|
+
"@memberjunction/ng-base-types": "5.45.0",
|
|
43
|
+
"@memberjunction/ng-container-directives": "5.45.0",
|
|
44
|
+
"@memberjunction/ng-shared": "5.45.0",
|
|
45
|
+
"@memberjunction/ng-shared-generic": "5.45.0",
|
|
46
|
+
"@memberjunction/ng-ui-components": "5.45.0",
|
|
46
47
|
"tslib": "^2.8.1"
|
|
47
48
|
},
|
|
48
49
|
"sideEffects": false,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE9C,QAAQ,CAAC,oCAAoC,EAAE,GAAG,EAAE;IAClD,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { describe, it, expect } from 'vitest';\n\ndescribe('@memberjunction/ng-list-management', () => {\n it('should have a passing test', () => {\n expect(true).toBe(true);\n });\n});\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"list-models.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/list-models.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
describe('ListManagementDialogConfig', () => {
|
|
3
|
-
it('should construct add mode config', () => {
|
|
4
|
-
const config = {
|
|
5
|
-
mode: 'add',
|
|
6
|
-
entityId: 'entity-1',
|
|
7
|
-
entityName: 'MJ: Users',
|
|
8
|
-
recordIds: ['rec-1', 'rec-2']
|
|
9
|
-
};
|
|
10
|
-
expect(config.mode).toBe('add');
|
|
11
|
-
expect(config.recordIds).toHaveLength(2);
|
|
12
|
-
});
|
|
13
|
-
it('should support all mode types', () => {
|
|
14
|
-
const modes = ['add', 'remove', 'manage'];
|
|
15
|
-
expect(modes).toHaveLength(3);
|
|
16
|
-
});
|
|
17
|
-
it('should accept optional fields', () => {
|
|
18
|
-
const config = {
|
|
19
|
-
mode: 'manage',
|
|
20
|
-
entityId: 'e1',
|
|
21
|
-
entityName: 'Items',
|
|
22
|
-
recordIds: ['r1'],
|
|
23
|
-
allowCreate: true,
|
|
24
|
-
allowRemove: true,
|
|
25
|
-
showMembership: false,
|
|
26
|
-
preSelectedListIds: ['l1'],
|
|
27
|
-
dialogTitle: 'Manage Lists'
|
|
28
|
-
};
|
|
29
|
-
expect(config.allowCreate).toBe(true);
|
|
30
|
-
expect(config.dialogTitle).toBe('Manage Lists');
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
describe('ListManagementResult', () => {
|
|
34
|
-
it('should represent cancelled result', () => {
|
|
35
|
-
const result = {
|
|
36
|
-
action: 'cancel',
|
|
37
|
-
added: [],
|
|
38
|
-
removed: [],
|
|
39
|
-
newListsCreated: []
|
|
40
|
-
};
|
|
41
|
-
expect(result.action).toBe('cancel');
|
|
42
|
-
});
|
|
43
|
-
it('should represent applied result with operations', () => {
|
|
44
|
-
const result = {
|
|
45
|
-
action: 'apply',
|
|
46
|
-
added: [{ listId: 'l1', listName: 'Favorites', recordIds: ['r1'] }],
|
|
47
|
-
removed: [],
|
|
48
|
-
newListsCreated: []
|
|
49
|
-
};
|
|
50
|
-
expect(result.added).toHaveLength(1);
|
|
51
|
-
expect(result.added[0].listName).toBe('Favorites');
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
describe('BatchOperationResult', () => {
|
|
55
|
-
it('should track success, failed, and skipped counts', () => {
|
|
56
|
-
const result = {
|
|
57
|
-
success: 8,
|
|
58
|
-
failed: 1,
|
|
59
|
-
skipped: 2,
|
|
60
|
-
errors: ['Record r5 already in list']
|
|
61
|
-
};
|
|
62
|
-
expect(result.success).toBe(8);
|
|
63
|
-
expect(result.errors).toHaveLength(1);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
describe('Filter and sort types', () => {
|
|
67
|
-
it('should support all filter tabs', () => {
|
|
68
|
-
const tabs = ['all', 'my-lists', 'shared', 'recent'];
|
|
69
|
-
expect(tabs).toHaveLength(4);
|
|
70
|
-
});
|
|
71
|
-
it('should support all sort options', () => {
|
|
72
|
-
const sorts = ['name', 'recent', 'item-count'];
|
|
73
|
-
expect(sorts).toHaveLength(3);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
describe('CreateListConfig', () => {
|
|
77
|
-
it('should construct with required fields', () => {
|
|
78
|
-
const config = {
|
|
79
|
-
name: 'My New List',
|
|
80
|
-
entityId: 'e1'
|
|
81
|
-
};
|
|
82
|
-
expect(config.name).toBe('My New List');
|
|
83
|
-
expect(config.description).toBeUndefined();
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
//# sourceMappingURL=list-models.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"list-models.test.js","sourceRoot":"","sources":["../../src/__tests__/list-models.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect } from 'vitest';\nimport type {\n ListManagementDialogConfig,\n ListManagementResult,\n ListOperationDetail,\n ListItemViewModel,\n ListFilterTab,\n ListSortOption,\n CreateListConfig,\n BatchOperationResult,\n RecordMembershipInfo\n} from '../lib/models/list-management.models';\n\ndescribe('ListManagementDialogConfig', () => {\n it('should construct add mode config', () => {\n const config: ListManagementDialogConfig = {\n mode: 'add',\n entityId: 'entity-1',\n entityName: 'MJ: Users',\n recordIds: ['rec-1', 'rec-2']\n };\n expect(config.mode).toBe('add');\n expect(config.recordIds).toHaveLength(2);\n });\n\n it('should support all mode types', () => {\n const modes: ListManagementDialogConfig['mode'][] = ['add', 'remove', 'manage'];\n expect(modes).toHaveLength(3);\n });\n\n it('should accept optional fields', () => {\n const config: ListManagementDialogConfig = {\n mode: 'manage',\n entityId: 'e1',\n entityName: 'Items',\n recordIds: ['r1'],\n allowCreate: true,\n allowRemove: true,\n showMembership: false,\n preSelectedListIds: ['l1'],\n dialogTitle: 'Manage Lists'\n };\n expect(config.allowCreate).toBe(true);\n expect(config.dialogTitle).toBe('Manage Lists');\n });\n});\n\ndescribe('ListManagementResult', () => {\n it('should represent cancelled result', () => {\n const result: ListManagementResult = {\n action: 'cancel',\n added: [],\n removed: [],\n newListsCreated: []\n };\n expect(result.action).toBe('cancel');\n });\n\n it('should represent applied result with operations', () => {\n const result: ListManagementResult = {\n action: 'apply',\n added: [{ listId: 'l1', listName: 'Favorites', recordIds: ['r1'] }],\n removed: [],\n newListsCreated: []\n };\n expect(result.added).toHaveLength(1);\n expect(result.added[0].listName).toBe('Favorites');\n });\n});\n\ndescribe('BatchOperationResult', () => {\n it('should track success, failed, and skipped counts', () => {\n const result: BatchOperationResult = {\n success: 8,\n failed: 1,\n skipped: 2,\n errors: ['Record r5 already in list']\n };\n expect(result.success).toBe(8);\n expect(result.errors).toHaveLength(1);\n });\n});\n\ndescribe('Filter and sort types', () => {\n it('should support all filter tabs', () => {\n const tabs: ListFilterTab[] = ['all', 'my-lists', 'shared', 'recent'];\n expect(tabs).toHaveLength(4);\n });\n\n it('should support all sort options', () => {\n const sorts: ListSortOption[] = ['name', 'recent', 'item-count'];\n expect(sorts).toHaveLength(3);\n });\n});\n\ndescribe('CreateListConfig', () => {\n it('should construct with required fields', () => {\n const config: CreateListConfig = {\n name: 'My New List',\n entityId: 'e1'\n };\n expect(config.name).toBe('My New List');\n expect(config.description).toBeUndefined();\n });\n});\n"]}
|
package/dist/lib/components/list-invitations/__tests__/list-invitations.component.test.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"list-invitations.component.test.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/list-invitations/__tests__/list-invitations.component.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
/**
|
|
3
|
-
* Recompute-trigger coverage for ListInvitationsComponent.visibleInvitations
|
|
4
|
-
* (PR #2841): the precomputed per-tab list is rebuilt only when the source data
|
|
5
|
-
* (`invitations`) or the active tab changes — NOT on every change-detection pass
|
|
6
|
-
* (a getter there would allocate a fresh map+filter each CD tick).
|
|
7
|
-
*
|
|
8
|
-
* The recompute also hydrates the Pending→Expired client boundary: a Pending
|
|
9
|
-
* invitation whose ExpiresAt is in the past surfaces under the Expired tab
|
|
10
|
-
* without waiting for the server-side flip.
|
|
11
|
-
*
|
|
12
|
-
* Pins:
|
|
13
|
-
* (1) recompute runs on data load (loadInvitations calls it),
|
|
14
|
-
* (2) recompute runs on tab change (setTab),
|
|
15
|
-
* (3) the Pending→Expired hydration boundary is applied during recompute,
|
|
16
|
-
* (4) visibleInvitations is a precomputed FIELD (stable identity between reads),
|
|
17
|
-
* (5) countFor reflects the same hydration boundary.
|
|
18
|
-
*
|
|
19
|
-
* inject(ChangeDetectorRef) is stubbed; BaseAngularComponent is mocked.
|
|
20
|
-
*/
|
|
21
|
-
vi.mock('@angular/core', () => ({
|
|
22
|
-
Component: () => (t) => t,
|
|
23
|
-
Input: () => () => { },
|
|
24
|
-
Output: () => () => { },
|
|
25
|
-
EventEmitter: class {
|
|
26
|
-
emit() { }
|
|
27
|
-
},
|
|
28
|
-
ChangeDetectorRef: class {
|
|
29
|
-
detectChanges() { }
|
|
30
|
-
markForCheck() { }
|
|
31
|
-
},
|
|
32
|
-
ChangeDetectionStrategy: { OnPush: 1 },
|
|
33
|
-
OnInit: class {
|
|
34
|
-
},
|
|
35
|
-
inject: () => ({ detectChanges() { }, markForCheck() { } }),
|
|
36
|
-
}));
|
|
37
|
-
vi.mock('@memberjunction/ng-base-types', () => {
|
|
38
|
-
class MockBaseAngularComponent {
|
|
39
|
-
Provider = null;
|
|
40
|
-
get ProviderToUse() { return this.Provider; }
|
|
41
|
-
}
|
|
42
|
-
return { BaseAngularComponent: MockBaseAngularComponent };
|
|
43
|
-
});
|
|
44
|
-
vi.mock('@memberjunction/core', () => ({ RunView: class {
|
|
45
|
-
} }));
|
|
46
|
-
vi.mock('@memberjunction/graphql-dataprovider', () => ({
|
|
47
|
-
GraphQLDataProvider: class {
|
|
48
|
-
},
|
|
49
|
-
GraphQLListsClient: class {
|
|
50
|
-
},
|
|
51
|
-
}));
|
|
52
|
-
import { ListInvitationsComponent } from '../list-invitations.component';
|
|
53
|
-
function row(id, status, expiresInMs) {
|
|
54
|
-
return {
|
|
55
|
-
ID: id,
|
|
56
|
-
Email: `${id}@example.com`,
|
|
57
|
-
Role: 'Viewer',
|
|
58
|
-
Status: status,
|
|
59
|
-
ExpiresAt: new Date(Date.now() + expiresInMs),
|
|
60
|
-
CreatedAt: new Date(),
|
|
61
|
-
Token: `tok-${id}`,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
function makeComponent() {
|
|
65
|
-
return new ListInvitationsComponent();
|
|
66
|
-
}
|
|
67
|
-
/** Set source data + run the (private) recompute, mirroring loadInvitations(). */
|
|
68
|
-
function loadData(component, rows) {
|
|
69
|
-
component.invitations = rows;
|
|
70
|
-
component.recomputeVisibleInvitations();
|
|
71
|
-
}
|
|
72
|
-
const HOUR = 3_600_000;
|
|
73
|
-
describe('ListInvitationsComponent — visibleInvitations recompute', () => {
|
|
74
|
-
let component;
|
|
75
|
-
beforeEach(() => {
|
|
76
|
-
component = makeComponent();
|
|
77
|
-
});
|
|
78
|
-
it('recomputes visibleInvitations on data load (default Pending tab)', () => {
|
|
79
|
-
loadData(component, [
|
|
80
|
-
row('p1', 'Pending', +HOUR),
|
|
81
|
-
row('a1', 'Accepted', +HOUR),
|
|
82
|
-
]);
|
|
83
|
-
expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['p1']);
|
|
84
|
-
});
|
|
85
|
-
it('recomputes when the active tab changes (setTab)', () => {
|
|
86
|
-
loadData(component, [
|
|
87
|
-
row('p1', 'Pending', +HOUR),
|
|
88
|
-
row('a1', 'Accepted', +HOUR),
|
|
89
|
-
row('r1', 'Revoked', +HOUR),
|
|
90
|
-
]);
|
|
91
|
-
expect(component.activeTab).toBe('Pending');
|
|
92
|
-
expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['p1']);
|
|
93
|
-
component.setTab('Accepted');
|
|
94
|
-
expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['a1']);
|
|
95
|
-
component.setTab('Revoked');
|
|
96
|
-
expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['r1']);
|
|
97
|
-
});
|
|
98
|
-
it('hydrates a past-due Pending invite as Expired (the client boundary)', () => {
|
|
99
|
-
loadData(component, [
|
|
100
|
-
row('p-live', 'Pending', +HOUR), // still valid
|
|
101
|
-
row('p-stale', 'Pending', -HOUR), // expired in the past
|
|
102
|
-
]);
|
|
103
|
-
// Pending tab shows only the still-live one
|
|
104
|
-
expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['p-live']);
|
|
105
|
-
// Expired tab surfaces the past-due Pending one without a server flip
|
|
106
|
-
component.setTab('Expired');
|
|
107
|
-
expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['p-stale']);
|
|
108
|
-
});
|
|
109
|
-
it('does NOT reclassify an Accepted/Revoked row even when its ExpiresAt is in the past', () => {
|
|
110
|
-
loadData(component, [
|
|
111
|
-
row('a-old', 'Accepted', -HOUR),
|
|
112
|
-
row('r-old', 'Revoked', -HOUR),
|
|
113
|
-
]);
|
|
114
|
-
component.setTab('Expired');
|
|
115
|
-
expect(component.visibleInvitations).toHaveLength(0);
|
|
116
|
-
component.setTab('Accepted');
|
|
117
|
-
expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['a-old']);
|
|
118
|
-
component.setTab('Revoked');
|
|
119
|
-
expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['r-old']);
|
|
120
|
-
});
|
|
121
|
-
it('countFor applies the same Pending→Expired hydration boundary', () => {
|
|
122
|
-
loadData(component, [
|
|
123
|
-
row('p-live', 'Pending', +HOUR),
|
|
124
|
-
row('p-stale', 'Pending', -HOUR),
|
|
125
|
-
row('a1', 'Accepted', +HOUR),
|
|
126
|
-
]);
|
|
127
|
-
expect(component.countFor('Pending')).toBe(1); // only the live one
|
|
128
|
-
expect(component.countFor('Expired')).toBe(1); // the stale Pending one
|
|
129
|
-
expect(component.countFor('Accepted')).toBe(1);
|
|
130
|
-
expect(component.countFor('Revoked')).toBe(0);
|
|
131
|
-
});
|
|
132
|
-
it('visibleInvitations is a precomputed field — re-reading it (no recompute) returns the same array', () => {
|
|
133
|
-
loadData(component, [row('p1', 'Pending', +HOUR)]);
|
|
134
|
-
const first = component.visibleInvitations;
|
|
135
|
-
const second = component.visibleInvitations;
|
|
136
|
-
expect(second).toBe(first); // stable identity (not re-allocated per access)
|
|
137
|
-
});
|
|
138
|
-
it('re-load with new data refreshes the visible list', () => {
|
|
139
|
-
loadData(component, [row('p1', 'Pending', +HOUR)]);
|
|
140
|
-
expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['p1']);
|
|
141
|
-
loadData(component, [row('p2', 'Pending', +HOUR), row('p3', 'Pending', +HOUR)]);
|
|
142
|
-
expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['p2', 'p3']);
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
//# sourceMappingURL=list-invitations.component.test.js.map
|
package/dist/lib/components/list-invitations/__tests__/list-invitations.component.test.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"list-invitations.component.test.js","sourceRoot":"","sources":["../../../../../src/lib/components/list-invitations/__tests__/list-invitations.component.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9D;;;;;;;;;;;;;;;;;;GAkBG;AAEH,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9B,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,CAAW,EAAE,EAAE,CAAC,CAAC;IACnC,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;QAAQ,aAAa,KAAI,CAAC;QAAC,YAAY,KAAI,CAAC;KAAE;IACjE,uBAAuB,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE;IACtC,MAAM,EAAE;KAAQ;IAChB,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,aAAa,KAAI,CAAC,EAAE,YAAY,KAAI,CAAC,EAAE,CAAC;CAC1D,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC5C,MAAM,wBAAwB;QAC5B,QAAQ,GAAY,IAAI,CAAC;QACzB,IAAI,aAAa,KAAc,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;KACvD;IACD,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,CAAC;AAC5D,CAAC,CAAC,CAAC;AAEH,EAAE,CAAC,IAAI,CAAC,sBAAsB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE;KAAQ,EAAE,CAAC,CAAC,CAAC;AAC/D,EAAE,CAAC,IAAI,CAAC,sCAAsC,EAAE,GAAG,EAAE,CAAC,CAAC;IACrD,mBAAmB,EAAE;KAAQ;IAC7B,kBAAkB,EAAE;KAAQ;CAC7B,CAAC,CAAC,CAAC;AAEJ,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAazE,SAAS,GAAG,CAAC,EAAU,EAAE,MAAc,EAAE,WAAmB;IAC1D,OAAO;QACL,EAAE,EAAE,EAAE;QACN,KAAK,EAAE,GAAG,EAAE,cAAc;QAC1B,IAAI,EAAE,QAAQ;QACd,MAAM,EAAE,MAAM;QACd,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,WAAW,CAAC;QAC7C,SAAS,EAAE,IAAI,IAAI,EAAE;QACrB,KAAK,EAAE,OAAO,EAAE,EAAE;KACnB,CAAC;AACJ,CAAC;AAED,SAAS,aAAa;IACpB,OAAO,IAAI,wBAAwB,EAAE,CAAC;AACxC,CAAC;AAED,kFAAkF;AAClF,SAAS,QAAQ,CAAC,SAAmC,EAAE,IAAW;IAC/D,SAA+C,CAAC,WAAW,GAAG,IAAI,CAAC;IACnE,SAAoE,CAAC,2BAA2B,EAAE,CAAC;AACtG,CAAC;AAED,MAAM,IAAI,GAAG,SAAS,CAAC;AAEvB,QAAQ,CAAC,yDAAyD,EAAE,GAAG,EAAE;IACvE,IAAI,SAAmC,CAAC;IAExC,UAAU,CAAC,GAAG,EAAE;QACd,SAAS,GAAG,aAAa,EAAE,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,QAAQ,CAAC,SAAS,EAAE;YAClB,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,CAAC;YAC3B,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,IAAI,CAAC;SAC7B,CAAC,CAAC;QACH,MAAM,CAAC,SAAS,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,QAAQ,CAAC,SAAS,EAAE;YAClB,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,CAAC;YAC3B,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,IAAI,CAAC;YAC5B,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,CAAC;SAC5B,CAAC,CAAC;QACH,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5C,MAAM,CAAC,SAAS,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAEtE,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7B,MAAM,CAAC,SAAS,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAEtE,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC5B,MAAM,CAAC,SAAS,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,QAAQ,CAAC,SAAS,EAAE;YAClB,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,IAAI,CAAC,EAAK,cAAc;YAClD,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,CAAC,IAAI,CAAC,EAAI,sBAAsB;SAC3D,CAAC,CAAC;QACH,4CAA4C;QAC5C,MAAM,CAAC,SAAS,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QAE1E,sEAAsE;QACtE,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC5B,MAAM,CAAC,SAAS,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oFAAoF,EAAE,GAAG,EAAE;QAC5F,QAAQ,CAAC,SAAS,EAAE;YAClB,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC,IAAI,CAAC;YAC/B,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC,IAAI,CAAC;SAC/B,CAAC,CAAC;QACH,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC5B,MAAM,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACrD,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7B,MAAM,CAAC,SAAS,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;QACzE,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC5B,MAAM,CAAC,SAAS,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACtE,QAAQ,CAAC,SAAS,EAAE;YAClB,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,IAAI,CAAC;YAC/B,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,CAAC,IAAI,CAAC;YAChC,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,IAAI,CAAC;SAC7B,CAAC,CAAC;QACH,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAE,oBAAoB;QACpE,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAE,wBAAwB;QACxE,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/C,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iGAAiG,EAAE,GAAG,EAAE;QACzG,QAAQ,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnD,MAAM,KAAK,GAAG,SAAS,CAAC,kBAAkB,CAAC;QAC3C,MAAM,MAAM,GAAG,SAAS,CAAC,kBAAkB,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,gDAAgD;IAC9E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,QAAQ,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnD,MAAM,CAAC,SAAS,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACtE,QAAQ,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChF,MAAM,CAAC,SAAS,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { describe, it, expect, beforeEach, vi } from 'vitest';\n\n/**\n * Recompute-trigger coverage for ListInvitationsComponent.visibleInvitations\n * (PR #2841): the precomputed per-tab list is rebuilt only when the source data\n * (`invitations`) or the active tab changes — NOT on every change-detection pass\n * (a getter there would allocate a fresh map+filter each CD tick).\n *\n * The recompute also hydrates the Pending→Expired client boundary: a Pending\n * invitation whose ExpiresAt is in the past surfaces under the Expired tab\n * without waiting for the server-side flip.\n *\n * Pins:\n * (1) recompute runs on data load (loadInvitations calls it),\n * (2) recompute runs on tab change (setTab),\n * (3) the Pending→Expired hydration boundary is applied during recompute,\n * (4) visibleInvitations is a precomputed FIELD (stable identity between reads),\n * (5) countFor reflects the same hydration boundary.\n *\n * inject(ChangeDetectorRef) is stubbed; BaseAngularComponent is mocked.\n */\n\nvi.mock('@angular/core', () => ({\n Component: () => (t: Function) => t,\n Input: () => () => {},\n Output: () => () => {},\n EventEmitter: class { emit() {} },\n ChangeDetectorRef: class { detectChanges() {} markForCheck() {} },\n ChangeDetectionStrategy: { OnPush: 1 },\n OnInit: class {},\n inject: () => ({ detectChanges() {}, markForCheck() {} }),\n}));\n\nvi.mock('@memberjunction/ng-base-types', () => {\n class MockBaseAngularComponent {\n Provider: unknown = null;\n get ProviderToUse(): unknown { return this.Provider; }\n }\n return { BaseAngularComponent: MockBaseAngularComponent };\n});\n\nvi.mock('@memberjunction/core', () => ({ RunView: class {} }));\nvi.mock('@memberjunction/graphql-dataprovider', () => ({\n GraphQLDataProvider: class {},\n GraphQLListsClient: class {},\n}));\n\nimport { ListInvitationsComponent } from '../list-invitations.component';\n\ntype Status = 'Pending' | 'Accepted' | 'Expired' | 'Revoked';\ninterface Row {\n ID: string;\n Email: string;\n Role: 'Editor' | 'Viewer';\n Status: Status;\n ExpiresAt: Date;\n CreatedAt: Date;\n Token: string;\n}\n\nfunction row(id: string, status: Status, expiresInMs: number): Row {\n return {\n ID: id,\n Email: `${id}@example.com`,\n Role: 'Viewer',\n Status: status,\n ExpiresAt: new Date(Date.now() + expiresInMs),\n CreatedAt: new Date(),\n Token: `tok-${id}`,\n };\n}\n\nfunction makeComponent(): ListInvitationsComponent {\n return new ListInvitationsComponent();\n}\n\n/** Set source data + run the (private) recompute, mirroring loadInvitations(). */\nfunction loadData(component: ListInvitationsComponent, rows: Row[]): void {\n (component as unknown as { invitations: Row[] }).invitations = rows;\n (component as unknown as { recomputeVisibleInvitations: () => void }).recomputeVisibleInvitations();\n}\n\nconst HOUR = 3_600_000;\n\ndescribe('ListInvitationsComponent — visibleInvitations recompute', () => {\n let component: ListInvitationsComponent;\n\n beforeEach(() => {\n component = makeComponent();\n });\n\n it('recomputes visibleInvitations on data load (default Pending tab)', () => {\n loadData(component, [\n row('p1', 'Pending', +HOUR),\n row('a1', 'Accepted', +HOUR),\n ]);\n expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['p1']);\n });\n\n it('recomputes when the active tab changes (setTab)', () => {\n loadData(component, [\n row('p1', 'Pending', +HOUR),\n row('a1', 'Accepted', +HOUR),\n row('r1', 'Revoked', +HOUR),\n ]);\n expect(component.activeTab).toBe('Pending');\n expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['p1']);\n\n component.setTab('Accepted');\n expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['a1']);\n\n component.setTab('Revoked');\n expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['r1']);\n });\n\n it('hydrates a past-due Pending invite as Expired (the client boundary)', () => {\n loadData(component, [\n row('p-live', 'Pending', +HOUR), // still valid\n row('p-stale', 'Pending', -HOUR), // expired in the past\n ]);\n // Pending tab shows only the still-live one\n expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['p-live']);\n\n // Expired tab surfaces the past-due Pending one without a server flip\n component.setTab('Expired');\n expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['p-stale']);\n });\n\n it('does NOT reclassify an Accepted/Revoked row even when its ExpiresAt is in the past', () => {\n loadData(component, [\n row('a-old', 'Accepted', -HOUR),\n row('r-old', 'Revoked', -HOUR),\n ]);\n component.setTab('Expired');\n expect(component.visibleInvitations).toHaveLength(0);\n component.setTab('Accepted');\n expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['a-old']);\n component.setTab('Revoked');\n expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['r-old']);\n });\n\n it('countFor applies the same Pending→Expired hydration boundary', () => {\n loadData(component, [\n row('p-live', 'Pending', +HOUR),\n row('p-stale', 'Pending', -HOUR),\n row('a1', 'Accepted', +HOUR),\n ]);\n expect(component.countFor('Pending')).toBe(1); // only the live one\n expect(component.countFor('Expired')).toBe(1); // the stale Pending one\n expect(component.countFor('Accepted')).toBe(1);\n expect(component.countFor('Revoked')).toBe(0);\n });\n\n it('visibleInvitations is a precomputed field — re-reading it (no recompute) returns the same array', () => {\n loadData(component, [row('p1', 'Pending', +HOUR)]);\n const first = component.visibleInvitations;\n const second = component.visibleInvitations;\n expect(second).toBe(first); // stable identity (not re-allocated per access)\n });\n\n it('re-load with new data refreshes the visible list', () => {\n loadData(component, [row('p1', 'Pending', +HOUR)]);\n expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['p1']);\n loadData(component, [row('p2', 'Pending', +HOUR), row('p3', 'Pending', +HOUR)]);\n expect(component.visibleInvitations.map((i) => i.ID)).toEqual(['p2', 'p3']);\n });\n});\n"]}
|