Skip to content

Commit 29dd9c2

Browse files
committed
test(notifications): fix and improve
- Fix async tests broken by recent commits. - Add setContext and telemetry verification
1 parent 860b441 commit 29dd9c2

File tree

4 files changed

+64
-32
lines changed

4 files changed

+64
-32
lines changed

packages/core/src/notifications/controller.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,22 @@ import { telemetry } from '../shared/telemetry/telemetry'
4040
export class NotificationsController {
4141
public static readonly suggestedPollIntervalMs = 1000 * 60 * 10 // 10 minutes
4242

43-
public readonly storageKey: globalKey
44-
4543
/** Internal memory state that is written to global state upon modification. */
4644
private readonly state: NotificationsState
4745

4846
static #instance: NotificationsController | undefined
4947

5048
constructor(
5149
private readonly notificationsNode: NotificationsNode,
52-
private readonly fetcher: NotificationFetcher = new RemoteFetcher()
50+
private readonly fetcher: NotificationFetcher = new RemoteFetcher(),
51+
public readonly storageKey: globalKey = 'aws.notifications'
5352
) {
5453
if (!NotificationsController.#instance) {
5554
// Register on first creation only.
5655
registerDismissCommand()
5756
}
5857
NotificationsController.#instance = this
5958

60-
this.storageKey = 'aws.notifications'
6159
this.state = globals.globalState.tryGet<NotificationsState>(this.storageKey, NotificationsStateConstructor, {
6260
startUp: {},
6361
emergency: {},

packages/core/src/notifications/panelNode.ts

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -78,24 +78,23 @@ export class NotificationsNode implements TreeNode {
7878
}
7979

8080
public refresh(): void {
81-
if (!this.view) {
82-
throw new ToolkitError('NotificationsNode: TreeView accessed without being registered.')
83-
}
84-
8581
const totalNotifications = this.notificationCount()
86-
if (totalNotifications > 0) {
87-
this.view.badge = {
88-
tooltip: `${totalNotifications} notification${totalNotifications > 1 ? 's' : ''}`,
89-
value: totalNotifications,
82+
if (this.view) {
83+
if (totalNotifications > 0) {
84+
this.view.badge = {
85+
tooltip: `${totalNotifications} notification${totalNotifications > 1 ? 's' : ''}`,
86+
value: totalNotifications,
87+
}
88+
this.view.title = `${NotificationsNode.title} (${totalNotifications})`
89+
} else {
90+
this.view.badge = undefined
91+
this.view.title = NotificationsNode.title
9092
}
91-
this.view.title = `${NotificationsNode.title} (${totalNotifications})`
92-
void setContext(this.showContextStr, true)
9393
} else {
94-
this.view.badge = undefined
95-
this.view.title = NotificationsNode.title
96-
void setContext(this.showContextStr, false)
94+
logger.warn('NotificationsNode was refreshed but the view was not initialized!')
9795
}
9896

97+
void setContext(this.showContextStr, totalNotifications > 0)
9998
this.provider?.refresh()
10099
}
101100

@@ -240,7 +239,7 @@ export class NotificationsNode implements TreeNode {
240239
case 'updateAndReload':
241240
// Give things time to finish executing.
242241
globals.clock.setTimeout(() => {
243-
void this.updateAndReload(notification.displayIf.extensionId)
242+
return this.updateAndReload(notification.displayIf.extensionId)
244243
}, 1000)
245244
break
246245
case 'openUrl':

packages/core/src/test/notifications/controller.test.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import * as FakeTimers from '@sinonjs/fake-timers'
88
import assert from 'assert'
99
import sinon from 'sinon'
1010
import globals from '../../shared/extensionGlobals'
11-
import { randomUUID } from '../../shared'
11+
import { randomUUID } from '../../shared/crypto'
12+
import * as setContext from '../../shared/vscode/setContext'
1213
import { assertTelemetry, installFakeClock } from '../testUtil'
1314
import {
1415
NotificationFetcher,
@@ -89,7 +90,7 @@ describe('Notifications Controller', function () {
8990
beforeEach(async function () {
9091
panelNode.setNotifications([], [])
9192
fetcher = new TestFetcher()
92-
controller = new NotificationsController(panelNode, fetcher)
93+
controller = new NotificationsController(panelNode, fetcher, '_aws.test.notification' as any)
9394

9495
ruleEngineSpy = sinon.spy(ruleEngine, 'shouldDisplayNotification')
9596
focusPanelSpy = sinon.spy(panelNode, 'focusPanel')
@@ -107,6 +108,10 @@ describe('Notifications Controller', function () {
107108
})
108109

109110
it('can fetch and store startup notifications', async function () {
111+
// There seems to be a race condition with having a global spy object that is reset after each
112+
// test. Doesn't seem to affect the other ones, for some reason.
113+
const setContextSpy = sinon.spy(setContext, 'setContext')
114+
110115
const eTag = randomUUID()
111116
const content = {
112117
schemaVersion: '1.x',
@@ -135,9 +140,14 @@ describe('Notifications Controller', function () {
135140
assert.deepStrictEqual(panelNode.startUpNotifications, [content.notifications[0]])
136141
assert.equal(panelNode.getChildren().length, 1)
137142
assert.equal(focusPanelSpy.callCount, 0)
143+
assert.ok(setContextSpy.calledWithExactly('aws.toolkit.notifications.show', true))
144+
145+
setContextSpy.restore()
138146
})
139147

140148
it('can fetch and store emergency notifications', async function () {
149+
const setContextSpy = sinon.spy(setContext, 'setContext')
150+
141151
const eTag = randomUUID()
142152
const content = {
143153
schemaVersion: '1.x',
@@ -166,9 +176,14 @@ describe('Notifications Controller', function () {
166176
assert.deepStrictEqual(panelNode.emergencyNotifications, [content.notifications[0]])
167177
assert.equal(panelNode.getChildren().length, 1)
168178
assert.equal(focusPanelSpy.callCount, 1)
179+
assert.ok(setContextSpy.calledWithExactly('aws.toolkit.notifications.show', true))
180+
181+
setContextSpy.restore()
169182
})
170183

171184
it('can fetch and store both startup and emergency notifications', async function () {
185+
const setContextSpy = sinon.spy(setContext, 'setContext')
186+
172187
const eTag1 = randomUUID()
173188
const eTag2 = randomUUID()
174189
const startUpContent = {
@@ -224,9 +239,14 @@ describe('Notifications Controller', function () {
224239
assert.deepStrictEqual(panelNode.emergencyNotifications, [emergencyContent.notifications[0]])
225240
assert.equal(panelNode.getChildren().length, 2)
226241
assert.equal(focusPanelSpy.callCount, 1)
242+
assert.ok(setContextSpy.calledWithExactly('aws.toolkit.notifications.show', true))
243+
244+
setContextSpy.restore()
227245
})
228246

229247
it('dismisses a startup notification', async function () {
248+
const setContextSpy = sinon.spy(setContext, 'setContext')
249+
230250
const eTag = randomUUID()
231251
const content = {
232252
schemaVersion: '1.x',
@@ -241,6 +261,7 @@ describe('Notifications Controller', function () {
241261

242262
assert.equal(panelNode.getChildren().length, 2)
243263
assert.equal(panelNode.startUpNotifications.length, 2)
264+
assert.ok(setContextSpy.calledWithExactly('aws.toolkit.notifications.show', true))
244265

245266
assert.deepStrictEqual(await globals.globalState.get(controller.storageKey), {
246267
startUp: {
@@ -267,9 +288,13 @@ describe('Notifications Controller', function () {
267288

268289
assert.equal(panelNode.getChildren().length, 1)
269290
assert.equal(panelNode.startUpNotifications.length, 1)
291+
292+
setContextSpy.restore()
270293
})
271294

272295
it('does not redisplay dismissed notifications', async function () {
296+
const setContextSpy = sinon.spy(setContext, 'setContext')
297+
273298
const content = {
274299
schemaVersion: '1.x',
275300
notifications: [getValidTestNotification('id:startup1')],
@@ -281,9 +306,11 @@ describe('Notifications Controller', function () {
281306

282307
await controller.pollForStartUp(ruleEngine)
283308
assert.equal(panelNode.getChildren().length, 1)
309+
assert.deepStrictEqual(setContextSpy.lastCall.args, ['aws.toolkit.notifications.show', true])
284310

285311
await dismissNotification(content.notifications[0])
286312
assert.equal(panelNode.getChildren().length, 0)
313+
assert.deepStrictEqual(setContextSpy.lastCall.args, ['aws.toolkit.notifications.show', false])
287314

288315
content.notifications.push(getValidTestNotification('id:startup2'))
289316
fetcher.setStartUpContent({
@@ -305,6 +332,9 @@ describe('Notifications Controller', function () {
305332
})
306333

307334
assert.equal(panelNode.getChildren().length, 1)
335+
assert.deepStrictEqual(setContextSpy.lastCall.args, ['aws.toolkit.notifications.show', true])
336+
337+
setContextSpy.restore()
308338
})
309339

310340
it('does not refocus emergency notifications', async function () {
@@ -376,6 +406,8 @@ describe('Notifications Controller', function () {
376406
})
377407

378408
it('cleans out dismissed state', async function () {
409+
const setContextSpy = sinon.spy(setContext, 'setContext')
410+
379411
const startUpContent = {
380412
schemaVersion: '1.x',
381413
notifications: [getValidTestNotification('id:startup1')],
@@ -434,6 +466,7 @@ describe('Notifications Controller', function () {
434466
newlyReceived: [],
435467
})
436468
assert.equal(panelNode.getChildren().length, 1)
469+
assert.deepStrictEqual(setContextSpy.lastCall.args, ['aws.toolkit.notifications.show', true])
437470

438471
fetcher.setEmergencyContent({
439472
eTag: '1',
@@ -455,6 +488,9 @@ describe('Notifications Controller', function () {
455488
})
456489

457490
assert.equal(panelNode.getChildren().length, 0)
491+
assert.deepStrictEqual(setContextSpy.lastCall.args, ['aws.toolkit.notifications.show', false])
492+
493+
setContextSpy.restore()
458494
})
459495

460496
it('does not rethrow errors when fetching', async function () {

packages/core/src/test/notifications/rendering.test.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,20 @@ import { panelNode } from './controller.test'
1212
import { getTestWindow } from '../shared/vscode/window'
1313
import * as VsCodeUtils from '../../shared/utilities/vsCodeUtils'
1414
import { assertTextEditorContains, installFakeClock } from '../testUtil'
15+
import { waitUntil } from '../../shared/utilities/timeoutUtils'
16+
import globals from '../../shared/extensionGlobals'
1517

1618
describe('Notifications Rendering', function () {
1719
let sandbox: sinon.SinonSandbox
18-
let clock: FakeTimers.InstalledClock
19-
20-
before(function () {
21-
clock = installFakeClock()
22-
})
2320

2421
beforeEach(function () {
2522
sandbox = sinon.createSandbox()
2623
})
2724

2825
afterEach(function () {
29-
clock.reset()
3026
sandbox.restore()
3127
})
3228

33-
after(function () {
34-
clock.uninstall()
35-
})
36-
3729
// util to test txt pop-up under different senarios
3830
async function verifyTxtNotification(notification: ToolkitNotification) {
3931
const expectedContent = notification.uiRenderInstructions.content['en-US'].description
@@ -103,14 +95,21 @@ describe('Notifications Rendering', function () {
10395
message.selectItem('Update and Reload')
10496
})
10597
const excuteCommandStub = sandbox.stub(vscode.commands, 'executeCommand').resolves()
98+
const telemetrySpy = sandbox.spy(globals.telemetry, 'flushRecords')
10699
const notification = getModalNotification()
107-
await panelNode.openNotification(notification)
108100

109101
// Update and Reload is put on a timer so that other methods (e.g. telemetry) can finish.
110-
await clock.tickAsync(2000)
102+
const clock: FakeTimers.InstalledClock = installFakeClock()
103+
await panelNode.openNotification(notification)
104+
105+
await clock.tickAsync(1000)
106+
clock.uninstall()
107+
108+
await waitUntil(async () => excuteCommandStub.called, { interval: 5, timeout: 5000 })
111109

112110
assert.ok(excuteCommandStub.calledWith('workbench.extensions.installExtension', 'aws.toolkit.fake.extension'))
113111
assert.ok(excuteCommandStub.calledWith('workbench.action.reloadWindow'))
112+
assert.ok(telemetrySpy.calledOnce)
114113
})
115114

116115
it('executes openURL type button', async function () {

0 commit comments

Comments
 (0)