-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
### What does this PR do? Adds a notification system / store for history so the frontend can be notified and correctly retrieve the file history. It "refreshes" based on file change, deletion or creation. ### Screenshot / video of UI <!-- If this PR is changing UI, please include screenshots or screencasts showing the difference --> N/A ### What issues does this PR fix or reference? <!-- Include any related issues from Podman Desktop repository (or from another issue tracker). --> Closes #188 Part of #150 ### How to test this PR? <!-- Please explain steps to reproduce --> - [x] Covered by tests Signed-off-by: Charlie Drage <[email protected]>
- Loading branch information
Showing
9 changed files
with
385 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/********************************************************************** | ||
* Copyright (C) 2024 Red Hat, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
***********************************************************************/ | ||
|
||
import { beforeEach, describe, expect, test, vi } from 'vitest'; | ||
import os from 'os'; | ||
import type { Webview, FileSystemWatcher } from '@podman-desktop/api'; | ||
import { fs } from '@podman-desktop/api'; | ||
import { HistoryNotifier } from './historyNotifier'; | ||
|
||
vi.mock('@podman-desktop/api', () => { | ||
return { | ||
fs: { | ||
createFileSystemWatcher: () => ({ | ||
onDidCreate: vi.fn(), | ||
onDidDelete: vi.fn(), | ||
onDidChange: vi.fn(), | ||
}), | ||
}, | ||
window: { | ||
showErrorMessage: vi.fn(), | ||
}, | ||
}; | ||
}); | ||
|
||
vi.mock('node:fs', () => { | ||
return { | ||
existsSync: vi.fn(), | ||
promises: { | ||
readFile: vi.fn(), | ||
}, | ||
}; | ||
}); | ||
|
||
let historyMock: HistoryNotifier; | ||
|
||
beforeEach(() => { | ||
vi.resetAllMocks(); | ||
}); | ||
|
||
test('Expect postMessage to be called when doing .notify', async () => { | ||
const tmpDir = os.tmpdir(); | ||
console.log(tmpDir); | ||
const postMessageMock = vi.fn().mockResolvedValue(undefined); | ||
historyMock = new HistoryNotifier({ postMessage: postMessageMock } as unknown as Webview, '/tmp/foobar'); | ||
await historyMock.notify(); | ||
expect(postMessageMock).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
describe('Tests involving a file system change', () => { | ||
let onDidCreateListener: () => void; | ||
let onDidDeleteListener: () => void; | ||
let onDidChangeListener: () => void; | ||
|
||
beforeEach(() => { | ||
vi.spyOn(fs, 'createFileSystemWatcher').mockReturnValue({ | ||
onDidCreate: vi.fn().mockImplementation(listener => (onDidCreateListener = listener)), | ||
onDidDelete: vi.fn().mockImplementation(listener => (onDidDeleteListener = listener)), | ||
onDidChange: vi.fn().mockImplementation(listener => (onDidChangeListener = listener)), | ||
} as unknown as FileSystemWatcher); | ||
}); | ||
|
||
test('Expect notify to be called when onDidChange is triggered', async () => { | ||
const postMessageMock = vi.fn().mockResolvedValue(undefined); | ||
historyMock = new HistoryNotifier({ postMessage: postMessageMock } as unknown as Webview, '/tmp/foobar'); | ||
onDidChangeListener(); | ||
expect(postMessageMock).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
test('Expect notify to be called when onDidCreate is triggered', async () => { | ||
const postMessageMock = vi.fn().mockResolvedValue(undefined); | ||
historyMock = new HistoryNotifier({ postMessage: postMessageMock } as unknown as Webview, '/tmp/foobar'); | ||
onDidCreateListener(); | ||
expect(postMessageMock).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
test('Expect notify to be called when onDidDelete is triggered', async () => { | ||
const postMessageMock = vi.fn().mockResolvedValue(undefined); | ||
historyMock = new HistoryNotifier({ postMessage: postMessageMock } as unknown as Webview, '/tmp/foobar'); | ||
onDidDeleteListener(); | ||
expect(postMessageMock).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
test('Expect notify to be called 3 times if file system watcher events are triggered 3 times', async () => { | ||
const postMessageMock = vi.fn().mockResolvedValue(undefined); | ||
historyMock = new HistoryNotifier({ postMessage: postMessageMock } as unknown as Webview, '/tmp/foobar'); | ||
onDidChangeListener(); | ||
onDidCreateListener(); | ||
onDidDeleteListener(); | ||
expect(postMessageMock).toHaveBeenCalledTimes(3); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/********************************************************************** | ||
* Copyright (C) 2024 Red Hat, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
***********************************************************************/ | ||
|
||
import { type Disposable, type Webview } from '@podman-desktop/api'; | ||
import * as podmanDesktopApi from '@podman-desktop/api'; | ||
import path from 'node:path'; | ||
import { Messages } from '/@shared/src/messages/Messages'; | ||
import { BOOTC_HISTORY_FILENAME } from '../history'; | ||
|
||
export class HistoryNotifier implements Disposable { | ||
#watcher: podmanDesktopApi.FileSystemWatcher; | ||
|
||
constructor( | ||
private webview: Webview, | ||
private readonly storagePath: string, | ||
) { | ||
this.#watcher = podmanDesktopApi.fs.createFileSystemWatcher(path.join(this.storagePath, BOOTC_HISTORY_FILENAME)); | ||
this.#watcher.onDidChange(this.notify.bind(this)); | ||
this.#watcher.onDidCreate(this.notify.bind(this)); | ||
this.#watcher.onDidDelete(this.notify.bind(this)); | ||
} | ||
|
||
async notify(): Promise<void> { | ||
await this.webview.postMessage({ | ||
id: Messages.MSG_HISTORY_UPDATE, | ||
}); | ||
} | ||
|
||
public async dispose(): Promise<void> { | ||
this.#watcher.dispose(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import type { Readable } from 'svelte/store'; | ||
import { Messages } from '/@shared/src/messages/Messages'; | ||
import { bootcClient } from '/@/api/client'; | ||
import type { BootcBuildInfo } from '/@shared/src/models/bootc'; | ||
import { RPCReadable } from '/@/stores/rpcReadable'; | ||
|
||
export const historyInfo: Readable<BootcBuildInfo[]> = RPCReadable<BootcBuildInfo[]>( | ||
[], | ||
[Messages.MSG_HISTORY_UPDATE], | ||
bootcClient.listHistoryInfo, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/********************************************************************** | ||
* Copyright (C) 2024 Red Hat, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
***********************************************************************/ | ||
|
||
import { beforeEach, expect, test, vi } from 'vitest'; | ||
import { RpcBrowser } from '/@shared/src/messages/MessageProxy'; | ||
import { RPCReadable } from './rpcReadable'; | ||
import { bootcClient, rpcBrowser } from '/@/api/client'; | ||
vi.mock('/@/api/client', async () => { | ||
const window = { | ||
addEventListener: (_: string, _f: (message: unknown) => void) => {}, | ||
} as unknown as Window; | ||
|
||
const api = { | ||
postMessage: (message: unknown) => { | ||
if (message && typeof message === 'object' && 'channel' in message) { | ||
const f = rpcBrowser.subscribers.get(message.channel as string); | ||
f?.(''); | ||
} | ||
}, | ||
} as unknown as PodmanDesktopApi; | ||
|
||
const rpcBrowser = new RpcBrowser(window, api); | ||
|
||
return { | ||
rpcBrowser: rpcBrowser, | ||
bootcClient: { | ||
listHistoryInfo: vi.fn(), | ||
}, | ||
}; | ||
}); | ||
|
||
beforeEach(() => { | ||
vi.clearAllMocks(); | ||
}); | ||
|
||
test('check updater is called once at subscription', async () => { | ||
const spyOnListHistoryInfo = vi.spyOn(bootcClient, 'listHistoryInfo'); | ||
const rpcWritable = RPCReadable<string[]>([], [], () => { | ||
bootcClient.listHistoryInfo(); | ||
return Promise.resolve(['']); | ||
}); | ||
rpcWritable.subscribe(_ => {}); | ||
expect(spyOnListHistoryInfo).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
test('check updater is called twice if there is one event fired', async () => { | ||
const spyOnListHistoryInfo = vi.spyOn(bootcClient, 'listHistoryInfo'); | ||
const rpcWritable = RPCReadable<string[]>([], ['event'], () => { | ||
bootcClient.listHistoryInfo(); | ||
return Promise.resolve(['']); | ||
}); | ||
rpcWritable.subscribe(_ => {}); | ||
rpcBrowser.invoke('event'); | ||
// wait for the timeout in the debouncer | ||
await new Promise(resolve => setTimeout(resolve, 600)); | ||
expect(spyOnListHistoryInfo).toHaveBeenCalledTimes(2); | ||
}); | ||
|
||
test('check updater is called only twice because of the debouncer if there is more than one event in a row', async () => { | ||
const spyOnListHistoryInfo = vi.spyOn(bootcClient, 'listHistoryInfo'); | ||
const rpcWritable = RPCReadable<string[]>([], ['event2'], () => { | ||
bootcClient.listHistoryInfo(); | ||
return Promise.resolve(['']); | ||
}); | ||
rpcWritable.subscribe(_ => {}); | ||
rpcBrowser.invoke('event2'); | ||
rpcBrowser.invoke('event2'); | ||
rpcBrowser.invoke('event2'); | ||
rpcBrowser.invoke('event2'); | ||
// wait for the timeout in the debouncer | ||
await new Promise(resolve => setTimeout(resolve, 600)); | ||
expect(spyOnListHistoryInfo).toHaveBeenCalledTimes(2); | ||
}); |
Oops, something went wrong.