@rancher/shell 3.0.7 → 3.0.8-rc.1

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.
Files changed (83) hide show
  1. package/assets/images/vendor/githubapp.svg +13 -0
  2. package/assets/styles/base/_typography.scss +1 -1
  3. package/assets/styles/themes/_modern.scss +5 -5
  4. package/assets/translations/en-us.yaml +91 -11
  5. package/assets/translations/zh-hans.yaml +0 -4
  6. package/components/Inactivity.vue +222 -106
  7. package/components/InstallHelmCharts.vue +2 -2
  8. package/components/ResourceDetail/index.vue +1 -1
  9. package/components/SortableTable/index.vue +17 -2
  10. package/components/fleet/FleetConfigMapSelector.vue +117 -0
  11. package/components/fleet/FleetSecretSelector.vue +127 -0
  12. package/components/fleet/__tests__/FleetConfigMapSelector.test.ts +125 -0
  13. package/components/fleet/__tests__/FleetSecretSelector.test.ts +82 -0
  14. package/components/form/FileImageSelector.vue +13 -4
  15. package/components/form/FileSelector.vue +11 -2
  16. package/components/form/ResourceLabeledSelect.vue +1 -0
  17. package/components/form/__tests__/ResourceLabeledSelect.test.ts +90 -0
  18. package/components/nav/Header.vue +1 -0
  19. package/config/product/auth.js +1 -0
  20. package/config/query-params.js +1 -0
  21. package/config/settings.ts +8 -1
  22. package/config/types.js +2 -0
  23. package/dialog/AddonConfigConfirmationDialog.vue +45 -1
  24. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +52 -11
  25. package/edit/auth/AuthProviderWarningBanners.vue +14 -1
  26. package/edit/auth/github-app-steps.vue +97 -0
  27. package/edit/auth/github-steps.vue +75 -0
  28. package/edit/auth/github.vue +94 -65
  29. package/edit/fleet.cattle.io.helmop.vue +51 -2
  30. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +15 -5
  31. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +11 -9
  32. package/edit/provisioning.cattle.io.cluster/rke2.vue +56 -9
  33. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +28 -2
  34. package/list/projectsecret.vue +1 -1
  35. package/machine-config/azure.vue +1 -1
  36. package/mixins/chart.js +1 -1
  37. package/models/__tests__/chart.test.ts +17 -9
  38. package/models/__tests__/compliance.cattle.io.clusterscanprofile.spec.js +30 -0
  39. package/models/catalog.cattle.io.app.js +1 -1
  40. package/models/chart.js +3 -1
  41. package/models/compliance.cattle.io.clusterscanprofile.js +1 -1
  42. package/models/management.cattle.io.authconfig.js +1 -0
  43. package/package.json +2 -2
  44. package/pages/auth/login.vue +5 -2
  45. package/pages/auth/verify.vue +1 -1
  46. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +3 -2
  47. package/pages/c/_cluster/apps/charts/chart.vue +2 -2
  48. package/pages/c/_cluster/explorer/EventsTable.vue +89 -3
  49. package/pages/c/_cluster/explorer/tools/index.vue +3 -3
  50. package/pages/c/_cluster/settings/performance.vue +12 -25
  51. package/pages/home.vue +313 -12
  52. package/plugins/axios.js +2 -1
  53. package/plugins/dashboard-store/actions.js +1 -1
  54. package/plugins/dashboard-store/resource-class.js +17 -2
  55. package/plugins/steve/steve-pagination-utils.ts +2 -2
  56. package/scripts/extension/publish +1 -1
  57. package/store/auth.js +8 -3
  58. package/store/aws.js +8 -6
  59. package/store/features.js +1 -0
  60. package/store/index.js +9 -3
  61. package/store/prefs.js +6 -0
  62. package/types/kube/kube-api.ts +2 -1
  63. package/types/rancher/index.d.ts +1 -0
  64. package/types/resources/settings.d.ts +29 -7
  65. package/types/shell/index.d.ts +59 -0
  66. package/utils/__tests__/cluster.test.ts +379 -1
  67. package/utils/cluster.js +157 -3
  68. package/utils/dynamic-content/__tests__/config.test.ts +187 -0
  69. package/utils/dynamic-content/__tests__/index.test.ts +390 -0
  70. package/utils/dynamic-content/__tests__/info.test.ts +263 -0
  71. package/utils/dynamic-content/__tests__/new-release.test.ts +216 -0
  72. package/utils/dynamic-content/__tests__/support-notice.test.ts +262 -0
  73. package/utils/dynamic-content/__tests__/util.test.ts +235 -0
  74. package/utils/dynamic-content/config.ts +55 -0
  75. package/utils/dynamic-content/index.ts +273 -0
  76. package/utils/dynamic-content/info.ts +219 -0
  77. package/utils/dynamic-content/new-release.ts +126 -0
  78. package/utils/dynamic-content/support-notice.ts +169 -0
  79. package/utils/dynamic-content/types.d.ts +101 -0
  80. package/utils/dynamic-content/util.ts +122 -0
  81. package/utils/inactivity.ts +104 -0
  82. package/utils/pagination-utils.ts +19 -4
  83. package/utils/release-notes.ts +1 -1
@@ -0,0 +1,216 @@
1
+ import { processReleaseVersion } from '../new-release';
2
+ import { NotificationLevel } from '@shell/types/notifications';
3
+ import { READ_NEW_RELEASE } from '@shell/store/prefs';
4
+ import { Context, VersionInfo } from '../types';
5
+ import * as util from '../util'; // Import util to mock removeMatchingNotifications
6
+ import semver from 'semver';
7
+
8
+ describe('processReleaseVersion', () => {
9
+ let mockContext: Context;
10
+ let mockDispatch: jest.Mock;
11
+ let mockGetters: any;
12
+ let mockLogger: any;
13
+ let mockRemoveMatchingNotifications: jest.SpyInstance;
14
+
15
+ beforeEach(() => {
16
+ mockDispatch = jest.fn();
17
+ mockGetters = {
18
+ 'prefs/get': jest.fn(),
19
+ 'i18n/t': jest.fn((key: string, params?: any) => {
20
+ // Simple mock for i18n/t to return predictable strings
21
+ const { version, version1, version2 } = params || {};
22
+
23
+ if (key === 'dynamicContent.newRelease.title') return `A new Rancher release is available`;
24
+ if (key === 'dynamicContent.newRelease.message') return `Rancher ${ version } has been released`;
25
+ if (key === 'dynamicContent.newRelease.moreInfo') return `More Info`;
26
+ if (key === 'dynamicContent.multipleNewReleases.title') return 'New Rancher releases are available';
27
+ if (key === 'dynamicContent.multipleNewReleases.message') return `Message for ${ version1 } and ${ version2 }`;
28
+ if (key === 'dynamicContent.multipleNewReleases.moreInfo') return `More Info for ${ version }`;
29
+
30
+ return key;
31
+ }),
32
+ };
33
+ mockLogger = {
34
+ info: jest.fn(),
35
+ debug: jest.fn(),
36
+ error: jest.fn(),
37
+ };
38
+
39
+ mockContext = {
40
+ dispatch: mockDispatch,
41
+ getters: mockGetters,
42
+ axios: {},
43
+ logger: mockLogger,
44
+ isAdmin: true,
45
+ config: {
46
+ enabled: true,
47
+ debug: false,
48
+ log: false,
49
+ endpoint: '',
50
+ prime: false,
51
+ distribution: 'community',
52
+ },
53
+ settings: {
54
+ releaseNotesUrl: 'https://example.com/releases/v$version',
55
+ suseExtensions: [],
56
+ },
57
+ };
58
+
59
+ // Mock the utility function. Default: notification does not exist, so add it.
60
+ mockRemoveMatchingNotifications = jest.spyOn(util, 'removeMatchingNotifications')
61
+ .mockResolvedValue(false);
62
+ });
63
+
64
+ afterEach(() => {
65
+ jest.restoreAllMocks();
66
+ });
67
+
68
+ it('should return early if releaseInfo is null/undefined or empty', async() => {
69
+ const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
70
+
71
+ await processReleaseVersion(mockContext, null as any, versionInfo);
72
+ expect(mockDispatch).not.toHaveBeenCalled();
73
+ expect(mockLogger.info).not.toHaveBeenCalled();
74
+
75
+ await processReleaseVersion(mockContext, [], versionInfo);
76
+ expect(mockDispatch).not.toHaveBeenCalled();
77
+ expect(mockLogger.info).not.toHaveBeenCalled();
78
+ });
79
+
80
+ it('should return early if versionInfo is null/undefined or version is missing', async() => {
81
+ const releaseInfo = [{ name: '2.12.1' }];
82
+
83
+ await processReleaseVersion(mockContext, releaseInfo, null as any);
84
+ expect(mockDispatch).not.toHaveBeenCalled();
85
+ expect(mockLogger.info).not.toHaveBeenCalled();
86
+
87
+ await processReleaseVersion(mockContext, releaseInfo, { version: null as any, isPrime: false });
88
+ expect(mockDispatch).not.toHaveBeenCalled();
89
+ expect(mockLogger.info).not.toHaveBeenCalled();
90
+ });
91
+
92
+ it('should not add notification if no newer release exists', async() => {
93
+ const releaseInfo = [{ name: '2.12.0' }, { name: '2.11.0' }];
94
+ const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
95
+
96
+ await processReleaseVersion(mockContext, releaseInfo, versionInfo);
97
+
98
+ expect(mockLogger.info).not.toHaveBeenCalledWith(expect.stringContaining('Found a newer release'));
99
+ expect(mockDispatch).not.toHaveBeenCalled();
100
+ });
101
+
102
+ it('should add a single new release notification for a newer patch version', async() => {
103
+ const releaseInfo = [{ name: '2.12.1' }, { name: '2.12.0' }, { name: '2.11.0' }];
104
+ const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
105
+
106
+ await processReleaseVersion(mockContext, releaseInfo, versionInfo);
107
+
108
+ expect(mockLogger.info).toHaveBeenCalledWith('Found a newer release: 2.12.1');
109
+ expect(mockLogger.info).not.toHaveBeenCalledWith(expect.stringContaining('Also found a newer patch release')); // Because newer and newerPatch are the same
110
+ expect(mockDispatch).toHaveBeenCalledTimes(1);
111
+ expect(mockDispatch).toHaveBeenCalledWith('notifications/add', {
112
+ id: 'new-release-2.12.1',
113
+ level: NotificationLevel.Announcement,
114
+ title: 'A new Rancher release is available',
115
+ message: 'Rancher 2.12.1 has been released',
116
+ preference: {
117
+ key: READ_NEW_RELEASE,
118
+ value: '2.12.1',
119
+ },
120
+ primaryAction: {
121
+ label: 'More Info',
122
+ target: 'https://example.com/releases/v2.12.1',
123
+ },
124
+ });
125
+ expect(mockRemoveMatchingNotifications).toHaveBeenCalledWith(mockContext, 'new-release-', '2.12.1');
126
+ });
127
+
128
+ it('should add a single new release notification for a newer major/minor version (no patch)', async() => {
129
+ const releaseInfo = [{ name: '2.13.0' }, { name: '2.12.0' }];
130
+ const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
131
+
132
+ await processReleaseVersion(mockContext, releaseInfo, versionInfo);
133
+
134
+ expect(mockLogger.info).toHaveBeenCalledWith('Found a newer release: 2.13.0');
135
+ expect(mockLogger.info).not.toHaveBeenCalledWith(expect.stringContaining('Also found a newer patch release'));
136
+ expect(mockDispatch).toHaveBeenCalledTimes(1);
137
+ expect(mockDispatch).toHaveBeenCalledWith('notifications/add', {
138
+ id: 'new-release-2.13.0',
139
+ level: NotificationLevel.Announcement,
140
+ title: 'A new Rancher release is available',
141
+ message: 'Rancher 2.13.0 has been released',
142
+ preference: {
143
+ key: READ_NEW_RELEASE,
144
+ value: '2.13.0',
145
+ },
146
+ primaryAction: {
147
+ label: 'More Info',
148
+ target: 'https://example.com/releases/v2.13.0',
149
+ },
150
+ });
151
+ expect(mockRemoveMatchingNotifications).toHaveBeenCalledWith(mockContext, 'new-release-', '2.13.0');
152
+ });
153
+
154
+ it('should add a multiple new releases notification when both newer patch and newer major/minor exist', async() => {
155
+ const releaseInfo = [{ name: '2.13.0' }, { name: '2.12.1' }, { name: '2.12.0' }];
156
+ const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
157
+
158
+ await processReleaseVersion(mockContext, releaseInfo, versionInfo);
159
+
160
+ expect(mockLogger.info).toHaveBeenCalledWith('Found a newer release: 2.13.0');
161
+ expect(mockLogger.info).toHaveBeenCalledWith('Also found a newer patch release: 2.12.1');
162
+ expect(mockDispatch).toHaveBeenCalledTimes(1);
163
+ expect(mockDispatch).toHaveBeenCalledWith('notifications/add', {
164
+ id: 'new-release-2.12.1-2.13.0',
165
+ level: NotificationLevel.Announcement,
166
+ title: 'New Rancher releases are available',
167
+ message: 'Message for 2.12.1 and 2.13.0',
168
+ preference: {
169
+ key: READ_NEW_RELEASE,
170
+ value: '2.12.1-2.13.0',
171
+ },
172
+ primaryAction: {
173
+ label: 'More Info for 2.12.1',
174
+ target: 'https://example.com/releases/v2.12.1',
175
+ },
176
+ secondaryAction: {
177
+ label: 'More Info for 2.13.0',
178
+ target: 'https://example.com/releases/v2.13.0',
179
+ },
180
+ });
181
+ expect(mockRemoveMatchingNotifications).toHaveBeenCalledWith(mockContext, 'new-release-', '2.12.1-2.13.0');
182
+ });
183
+
184
+ it('should not add notification if it was already read (single release)', async() => {
185
+ mockGetters['prefs/get'].mockReturnValue('2.12.1'); // Simulate already read
186
+ const releaseInfo = [{ name: '2.12.1' }];
187
+ const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
188
+
189
+ await processReleaseVersion(mockContext, releaseInfo, versionInfo);
190
+
191
+ expect(mockDispatch).not.toHaveBeenCalled();
192
+ expect(mockLogger.debug).not.toHaveBeenCalledWith(expect.stringContaining('Adding new release notification'));
193
+ });
194
+
195
+ it('should not add notification if it was already read (multiple releases)', async() => {
196
+ mockGetters['prefs/get'].mockReturnValue('2.12.1-2.13.0'); // Simulate already read
197
+ const releaseInfo = [{ name: '2.13.0' }, { name: '2.12.1' }];
198
+ const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
199
+
200
+ await processReleaseVersion(mockContext, releaseInfo, versionInfo);
201
+
202
+ expect(mockDispatch).not.toHaveBeenCalled();
203
+ expect(mockLogger.info).not.toHaveBeenCalledWith(expect.stringContaining('Adding new multiple release notification'));
204
+ });
205
+
206
+ it('should not add notification if removeMatchingNotifications indicates it exists', async() => {
207
+ mockRemoveMatchingNotifications.mockResolvedValue(true); // Simulate notification already exists
208
+ const releaseInfo = [{ name: '2.12.1' }];
209
+ const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
210
+
211
+ await processReleaseVersion(mockContext, releaseInfo, versionInfo);
212
+
213
+ expect(mockDispatch).not.toHaveBeenCalled();
214
+ expect(mockLogger.debug).not.toHaveBeenCalledWith(expect.stringContaining('Adding new release notification'));
215
+ });
216
+ });
@@ -0,0 +1,262 @@
1
+ import { processSupportNotices } from '../support-notice';
2
+ import { NotificationLevel } from '@shell/types/notifications';
3
+ import { READ_SUPPORT_NOTICE, READ_UPCOMING_SUPPORT_NOTICE } from '@shell/store/prefs';
4
+ import { Context, VersionInfo, SupportInfo } from '../types';
5
+ import * as util from '../util'; // To mock removeMatchingNotifications
6
+ import semver from 'semver';
7
+ import day from 'dayjs';
8
+
9
+ describe('processSupportNotices', () => {
10
+ let mockContext: Context;
11
+ let mockDispatch: jest.Mock;
12
+ let mockGetters: any;
13
+ let mockLogger: any;
14
+ let mockRemoveMatchingNotifications: jest.SpyInstance;
15
+
16
+ beforeEach(() => {
17
+ mockDispatch = jest.fn();
18
+ mockGetters = {
19
+ 'prefs/get': jest.fn(),
20
+ 'i18n/t': jest.fn((key: string, params?: any) => {
21
+ const { version, days } = params || {};
22
+
23
+ if (key === 'dynamicContent.eol.title') return `EOL Title for ${ version }`;
24
+ if (key === 'dynamicContent.eol.message') return `EOL Message for ${ version }`;
25
+ if (key === 'dynamicContent.eom.title') return `EOM Title for ${ version }`;
26
+ if (key === 'dynamicContent.eom.message') return `EOM Message for ${ version }`;
27
+ if (key === 'dynamicContent.upcomingEol.title') return `Upcoming EOL Title for ${ version } in ${ days } days`;
28
+ if (key === 'dynamicContent.upcomingEol.message') return `Upcoming EOL Message for ${ version } in ${ days } days`;
29
+ if (key === 'dynamicContent.upcomingEom.title') return `Upcoming EOM Title for ${ version } in ${ days } days`;
30
+ if (key === 'dynamicContent.upcomingEom.message') return `Upcoming EOM Message for ${ version } in ${ days } days`;
31
+
32
+ return key;
33
+ }),
34
+ };
35
+ mockLogger = {
36
+ info: jest.fn(),
37
+ debug: jest.fn(),
38
+ error: jest.fn(),
39
+ };
40
+
41
+ mockContext = {
42
+ dispatch: mockDispatch,
43
+ getters: mockGetters,
44
+ axios: {},
45
+ logger: mockLogger,
46
+ isAdmin: true,
47
+ config: {
48
+ enabled: true,
49
+ debug: false,
50
+ log: false,
51
+ endpoint: '',
52
+ prime: false,
53
+ distribution: 'community',
54
+ },
55
+ settings: {
56
+ releaseNotesUrl: '',
57
+ suseExtensions: [],
58
+ },
59
+ };
60
+
61
+ // Mock the utility function. Default: notification does not exist, so add it.
62
+ mockRemoveMatchingNotifications = jest.spyOn(util, 'removeMatchingNotifications')
63
+ .mockResolvedValue(false);
64
+ });
65
+
66
+ afterEach(() => {
67
+ jest.restoreAllMocks();
68
+ });
69
+
70
+ it('should return early if statusInfo is null/undefined', async() => {
71
+ const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
72
+
73
+ await processSupportNotices(mockContext, null as any, versionInfo);
74
+ expect(mockDispatch).not.toHaveBeenCalled();
75
+ });
76
+
77
+ it('should return early if versionInfo is null/undefined or version is missing', async() => {
78
+ const statusInfo: SupportInfo = { status: { eol: '<= 2.11', eom: '<= 2.12' }, upcoming: {} as any };
79
+
80
+ await processSupportNotices(mockContext, statusInfo, null as any);
81
+ expect(mockDispatch).not.toHaveBeenCalled();
82
+ await processSupportNotices(mockContext, statusInfo, { version: null, isPrime: false });
83
+ expect(mockDispatch).not.toHaveBeenCalled();
84
+ });
85
+
86
+ it('should not add notification if no support status matches', async() => {
87
+ const versionInfo: VersionInfo = { version: semver.coerce('2.13.0')!, isPrime: false };
88
+ const statusInfo: SupportInfo = {
89
+ status: { eol: '<= 2.11.x', eom: '<= 2.12.x' },
90
+ upcoming: {
91
+ eom: { version: '= 2.13.x', date: day().add(40, 'day').toDate() },
92
+ eol: { version: '= 2.13.x', date: day().add(40, 'day').toDate() },
93
+ }
94
+ };
95
+
96
+ await processSupportNotices(mockContext, statusInfo, versionInfo);
97
+ expect(mockDispatch).not.toHaveBeenCalled();
98
+ expect(mockLogger.info).not.toHaveBeenCalled();
99
+ });
100
+
101
+ it('should add EOL notification if version is EOL', async() => {
102
+ const versionInfo: VersionInfo = { version: semver.coerce('2.11.5')!, isPrime: false };
103
+ const statusInfo: SupportInfo = {
104
+ status: { eol: '<= 2.11.x', eom: '<= 2.12.x' },
105
+ upcoming: {} as any
106
+ };
107
+
108
+ await processSupportNotices(mockContext, statusInfo, versionInfo);
109
+
110
+ expect(mockLogger.info).toHaveBeenCalledWith('This version (2.11.5) is End of Life');
111
+ expect(mockDispatch).toHaveBeenCalledWith('notifications/add', expect.objectContaining({
112
+ id: 'support-notice-eol-2.11',
113
+ level: NotificationLevel.Warning,
114
+ title: 'EOL Title for 2.11',
115
+ }));
116
+ });
117
+
118
+ it('should add EOM notification if version is EOM but not EOL', async() => {
119
+ const versionInfo: VersionInfo = { version: semver.coerce('2.12.3')!, isPrime: false };
120
+ const statusInfo: SupportInfo = {
121
+ status: { eol: '<= 2.11.x', eom: '<= 2.12.x' },
122
+ upcoming: {} as any
123
+ };
124
+
125
+ await processSupportNotices(mockContext, statusInfo, versionInfo);
126
+
127
+ expect(mockLogger.info).toHaveBeenCalledWith('This version (2.12.3) is End of Maintenance');
128
+ expect(mockDispatch).toHaveBeenCalledWith('notifications/add', expect.objectContaining({
129
+ id: 'support-notice-eom-2.12',
130
+ level: NotificationLevel.Warning,
131
+ title: 'EOM Title for 2.12',
132
+ }));
133
+ });
134
+
135
+ it('should add upcoming EOL notification', async() => {
136
+ const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
137
+ const statusInfo: SupportInfo = {
138
+ status: { eol: '<= 2.11.x', eom: '<= 2.11.x' },
139
+ upcoming: {
140
+ eol: { version: '= 2.12.x', date: day().add(15, 'day').toDate() },
141
+ eom: {} as any,
142
+ }
143
+ };
144
+
145
+ await processSupportNotices(mockContext, statusInfo, versionInfo);
146
+
147
+ expect(mockDispatch).toHaveBeenCalledWith('notifications/add', expect.objectContaining({
148
+ id: 'upcoming-support-notice-eol-2.12',
149
+ level: NotificationLevel.Warning,
150
+ title: 'Upcoming EOL Title for 2.12 in 15 days',
151
+ }));
152
+ });
153
+
154
+ it('should add upcoming EOM notification', async() => {
155
+ const versionInfo: VersionInfo = { version: semver.coerce('2.13.0')!, isPrime: false };
156
+ const statusInfo: SupportInfo = {
157
+ status: { eol: '<= 2.12.x', eom: '<= 2.12.x' },
158
+ upcoming: {
159
+ eom: {
160
+ version: '= 2.13.x', date: day().add(20, 'day').toDate(), noticeDays: 25
161
+ },
162
+ eol: {} as any,
163
+ }
164
+ };
165
+
166
+ await processSupportNotices(mockContext, statusInfo, versionInfo);
167
+
168
+ expect(mockDispatch).toHaveBeenCalledWith('notifications/add', expect.objectContaining({
169
+ id: 'upcoming-support-notice-eom-2.13',
170
+ level: NotificationLevel.Warning,
171
+ title: 'Upcoming EOM Title for 2.13 in 20 days',
172
+ }));
173
+ });
174
+
175
+ it('should not add notification if removeMatchingNotifications indicates it exists', async() => {
176
+ mockRemoveMatchingNotifications.mockResolvedValue(true); // Simulate notification already exists
177
+ const versionInfo: VersionInfo = { version: semver.coerce('2.11.5')!, isPrime: false };
178
+ const statusInfo: SupportInfo = {
179
+ status: { eol: '<= 2.11.x', eom: '<= 2.12.x' },
180
+ upcoming: {} as any
181
+ };
182
+
183
+ await processSupportNotices(mockContext, statusInfo, versionInfo);
184
+
185
+ expect(mockDispatch).not.toHaveBeenCalled();
186
+ });
187
+
188
+ describe('user preferences', () => {
189
+ it('should not add EOL notification if it was already read', async() => {
190
+ mockGetters['prefs/get'].mockImplementation((key: string) => {
191
+ if (key === READ_SUPPORT_NOTICE) return 'eol-2.11';
192
+
193
+ return '';
194
+ });
195
+ const versionInfo: VersionInfo = { version: semver.coerce('2.11.5')!, isPrime: false };
196
+ const statusInfo: SupportInfo = {
197
+ status: { eol: '<= 2.11.x', eom: '<= 2.12.x' },
198
+ upcoming: {} as any
199
+ };
200
+
201
+ await processSupportNotices(mockContext, statusInfo, versionInfo);
202
+
203
+ expect(mockDispatch).not.toHaveBeenCalled();
204
+ });
205
+
206
+ it('should not add EOM notification if it was already read', async() => {
207
+ mockGetters['prefs/get'].mockImplementation((key: string) => {
208
+ if (key === READ_SUPPORT_NOTICE) return 'eom-2.12';
209
+
210
+ return '';
211
+ });
212
+ const versionInfo: VersionInfo = { version: semver.coerce('2.12.3')!, isPrime: false };
213
+ const statusInfo: SupportInfo = {
214
+ status: { eol: '<= 2.11.x', eom: '<= 2.12.x' },
215
+ upcoming: {} as any
216
+ };
217
+
218
+ await processSupportNotices(mockContext, statusInfo, versionInfo);
219
+
220
+ expect(mockDispatch).not.toHaveBeenCalled();
221
+ });
222
+
223
+ it('should not add upcoming EOL notification if it was already read', async() => {
224
+ mockGetters['prefs/get'].mockImplementation((key: string) => {
225
+ if (key === READ_UPCOMING_SUPPORT_NOTICE) return 'eol-2.12';
226
+
227
+ return '';
228
+ });
229
+ const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
230
+ const statusInfo: SupportInfo = {
231
+ status: { eol: '<= 2.11.x', eom: '<= 2.11.x' },
232
+ upcoming: { eol: { version: '= 2.12.x', date: day().add(145, 'day').toDate() }, eom: {} as any }
233
+ };
234
+
235
+ await processSupportNotices(mockContext, statusInfo, versionInfo);
236
+
237
+ expect(mockDispatch).not.toHaveBeenCalled();
238
+ });
239
+
240
+ it('should not add upcoming EOM notification if it was already read', async() => {
241
+ mockGetters['prefs/get'].mockImplementation((key: string) => {
242
+ if (key === READ_UPCOMING_SUPPORT_NOTICE) return 'eom-2.13';
243
+
244
+ return '';
245
+ });
246
+ const versionInfo: VersionInfo = { version: semver.coerce('2.13.0')!, isPrime: false };
247
+ const statusInfo: SupportInfo = {
248
+ status: { eol: '<= 2.12.x', eom: '<= 2.12.x' },
249
+ upcoming: {
250
+ eom: {
251
+ version: '= 2.13.x', date: day().add(20, 'day').toDate(), noticeDays: 25
252
+ },
253
+ eol: {} as any
254
+ }
255
+ };
256
+
257
+ await processSupportNotices(mockContext, statusInfo, versionInfo);
258
+
259
+ expect(mockDispatch).not.toHaveBeenCalled();
260
+ });
261
+ });
262
+ });