Skip to content

Commit

Permalink
✨ Add replace reference with latest button in webview for individual …
Browse files Browse the repository at this point in the history
…snapshots
  • Loading branch information
juzerzarif committed May 7, 2021
1 parent f6dfd14 commit 38ebffd
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 42 deletions.
11 changes: 8 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@
"@types/glob": "^7.1.1",
"@types/jest": "^26.0.20",
"@types/jest-when": "^2.7.2",
"@types/lodash": "^4.14.168",
"@types/mocha": "^5.2.7",
"@types/mock-fs": "^4.13.0",
"@types/node": "^12.20.4",
Expand Down Expand Up @@ -233,6 +234,7 @@
"dependencies": {
"fast-glob": "^3.2.4",
"fs-extra": "^9.0.1",
"lodash": "^4.17.21",
"nanoid": "^3.1.20",
"rimraf": "^3.0.0",
"tslib": "^2.1.0",
Expand Down
18 changes: 16 additions & 2 deletions src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,21 @@ declare namespace WdioWebview {
extensionConfig: ExtensionConfig;
}

declare interface Message {
ready?: boolean;
declare interface BaseMessage {
intent: string;
[key: string]: unknown;
}

declare interface ReadyMessage extends BaseMessage {
intent: 'webviewReady';
ready: boolean;
}

declare interface ReplaceMessage extends BaseMessage {
intent: 'replaceReferenceWithLatest';
locale: string;
formFactor: string;
}

declare type Message = ReadyMessage | ReplaceMessage;
}
2 changes: 1 addition & 1 deletion src/webview-ui/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
const syncScroll = createScrollSync();
onMount(() => {
sendWebviewMessage({ ready: true })
sendWebviewMessage({ intent: 'webviewReady', ready: true });
})
$: {
Expand Down
32 changes: 31 additions & 1 deletion src/webview-ui/snapshot/SnapshotContainer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import SnapshotTab from './SnapshotTab.svelte';
import SnapshotTabBar from './SnapshotTabBar.svelte';
import { getContext } from 'svelte';
import { vsCodeWritable } from "../vscodeWebviewApi";
import { sendWebviewMessage, vsCodeWritable } from "../vscodeWebviewApi";
import type { WdioWebview } from '../../types';
import type { TabType } from './SnapshotTabBar.svelte';
Expand All @@ -17,12 +17,30 @@
const containerId = `${locale}-${formFactor}`;
const activeTab = vsCodeWritable<TabType>(`${containerId}-active-tab`, initialTab);
const tabIds = { reference: `${containerId}-reference`, latest: `${containerId}-latest`, diff: `${containerId}-diff` };
const handleReplaceReferenceClick = () => {
sendWebviewMessage({ intent: 'replaceReferenceWithLatest', locale, formFactor });
}
</script>

<div class=" mb-8 last:mb-0 border-b last:border-b-0" data-snapshot-container>
<div class="flex mb-2.5 px-2">
<h2 class="text-2xl font-medium mr-2.5">{resource.locale} | {resource.formFactor}</h2>
{#if resource.diff.exists} <DiffBadge /> {/if}
<button
disabled={!resource.diff.exists}
class="
btn
border-2 rounded border-current
text-sm font-medium
active:bg-gray-300 dark:active:bg-opacity-800 active:bg-opacity-30 disabled:active:bg-transparent
disabled:cursor-not-allowed disabled:opacity-30"
on:click={handleReplaceReferenceClick}
>
<div class="btn-content rounded px-2 h-full w-full flex items-center" tabindex={-1}>
Replace reference with latest
</div>
</button>
</div>
<SnapshotTabBar tabIds={tabIds} bind:activeTab={$activeTab} />
<div class="p-5 content">
Expand All @@ -48,4 +66,16 @@
.content {
height: 75vh;
}
.btn {
margin-left: auto;
}
.btn:focus, .btn-content:focus {
outline: none;
}
.btn:focus > .btn-content {
@apply ring ring-gray-400 ring-opacity-50 ring-offset-4 ring-offset-transparent;
}
</style>
8 changes: 5 additions & 3 deletions src/webview-ui/vscodeWebviewApi.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { onDestroy } from 'svelte';
import { writable } from 'svelte/store';
import type { JsonObject, JsonValue } from 'type-fest';
import type { JsonValue } from 'type-fest';
import type { Writable } from 'svelte/store';

import type { WdioWebview } from '../types';

declare global {
interface Window {
acquireVsCodeApi: () => {
Expand All @@ -26,6 +28,6 @@ export const vsCodeWritable = <T extends JsonValue>(key: string, initialValue: T
return stateStore;
};

export const sendWebviewMessage = (message: JsonObject | JsonValue): void => {
vscode.postMessage(message);
export const sendWebviewMessage = (message: WdioWebview.Message): void => {
vscode.postMessage(message as JsonValue);
};
68 changes: 48 additions & 20 deletions src/webview/WdioWebviewPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,23 @@ import * as path from 'path';

import * as fglob from 'fast-glob';
import * as fs from 'fs-extra';
import debounce from 'lodash/debounce';
import { Uri, ViewColumn, window } from 'vscode';
import { nanoid } from 'nanoid';
import type { DebouncedFunc } from 'lodash';
import type { Webview, WebviewPanel } from 'vscode';

import ExtensionState from '../common/ExtensionState';
import ResourceRetriever from '../common/ResourceRetriever';
import type WdioSnapshot from '../tree-view/WdioSnapshot';
import WdioSnapshot from '../tree-view/WdioSnapshot';
import { replaceReferenceWithLatest } from '../common/utils';
import type WorkspaceFolderItem from '../tree-view/WorkspaceFolder';
import type { WdioWebview } from '../types';
import type { WdioResource, WdioWebview } from '../types';

import html from './webview.template.html';

interface PanelImageData extends WdioWebview.ImageData {
fsPath: string;
interface WebviewSnapshotData extends WdioWebview.Snapshot {
resources: (WdioWebview.Resource & WdioResource)[];
}

class WdioWebviewPanel {
Expand Down Expand Up @@ -47,17 +50,16 @@ class WdioWebviewPanel {
const workspacePath = workspaceFolderItem?.resourceUri.fsPath;

this.openPanels.forEach((webviewPanel) => {
const { reference: _reference, locale, formFactor } = webviewPanel.webviewSnapshot.resources[0];
const reference = _reference as PanelImageData;
if (workspacePath && !reference.fsPath.startsWith(workspacePath)) {
const { reference, locale, formFactor } = webviewPanel.webviewSnapshot.resources[0];
if (workspacePath && !reference.uri.fsPath.startsWith(workspacePath)) {
return;
}

const snapshotFilename = path.basename(reference.fsPath);
const specFolderName = path.basename(path.dirname(reference.fsPath));
const referenceFolderPath = reference.fsPath.substring(
const snapshotFilename = path.basename(reference.uri.fsPath);
const specFolderName = path.basename(path.dirname(reference.uri.fsPath));
const referenceFolderPath = reference.uri.fsPath.substring(
0,
reference.fsPath.indexOf(path.join(path.sep, locale, formFactor, specFolderName, snapshotFilename))
reference.uri.fsPath.indexOf(path.join(path.sep, locale, formFactor, specFolderName, snapshotFilename))
);
const snapshotFolderPath = path.dirname(referenceFolderPath);
const allSnapshotFiles = fglob.sync(`**/${specFolderName}/${snapshotFilename}`, { cwd: referenceFolderPath });
Expand All @@ -79,7 +81,7 @@ class WdioWebviewPanel {
relativeSnapshotPaths: string[],
snapshotFolderPath: string,
webview: Webview
): WdioWebview.Resource[] {
): WebviewSnapshotData['resources'] {
return relativeSnapshotPaths.map((relativeSnapshotPath) => {
const localeFormFactorPath = path.dirname(path.dirname(relativeSnapshotPath));
const formFactor = path.basename(localeFormFactorPath);
Expand All @@ -100,7 +102,7 @@ class WdioWebviewPanel {
const imageUri = Uri.file(imagePath);
return {
src: `${webview.asWebviewUri(imageUri)}?${nanoid()}`,
fsPath: imageUri.fsPath,
uri: imageUri,
exists: imageExists,
};
}
Expand All @@ -110,7 +112,8 @@ class WdioWebviewPanel {
*/
public readonly id: string;
private readonly panel: WebviewPanel;
private webviewSnapshot: WdioWebview.Snapshot = {} as WdioWebview.Snapshot;
private readonly debouncedPostMessage: DebouncedFunc<WebviewPanel['webview']['postMessage']>;
private webviewSnapshot: WebviewSnapshotData = {} as WebviewSnapshotData;
private pendingPostMessage: WdioWebview.Data | null = null;
private webviewReady = false;

Expand All @@ -122,6 +125,7 @@ class WdioWebviewPanel {
});
this.panel.iconPath = ResourceRetriever.getIcon('snapshot_icon.png');
this.panel.webview.html = this.buildWebviewHTML();
this.debouncedPostMessage = debounce(this.panel.webview.postMessage, 500).bind(this.panel.webview);
this.panel.onDidDispose(
() => {
WdioWebviewPanel.openPanels = WdioWebviewPanel.openPanels.filter((panel) => panel !== this);
Expand All @@ -141,11 +145,10 @@ class WdioWebviewPanel {
);
this.panel.webview.onDidReceiveMessage(
(message: WdioWebview.Message) => {
this.webviewReady = !!message.ready;
/* istanbul ignore else */
if (this.webviewReady && this.pendingPostMessage) {
this.panel.webview.postMessage(this.pendingPostMessage);
this.pendingPostMessage = null;
if (message.intent === 'webviewReady') {
this.handleWebviewReady(message.ready);
} /* istanbul ignore else */ else if (message.intent === 'replaceReferenceWithLatest') {
this.replaceSingleReferenceWithLatest(message.locale, message.formFactor);
}
},
null,
Expand Down Expand Up @@ -181,11 +184,36 @@ class WdioWebviewPanel {
extensionConfig: ExtensionState.configuration,
};
if (this.webviewReady) {
this.panel.webview.postMessage(webviewData);
this.debouncedPostMessage(webviewData);
} else {
this.pendingPostMessage = webviewData;
}
}

private handleWebviewReady(webviewReady: boolean) {
this.webviewReady = webviewReady;
/* istanbul ignore else */
if (this.webviewReady && this.pendingPostMessage) {
this.panel.webview.postMessage(this.pendingPostMessage);
this.pendingPostMessage = null;
}
}

private replaceSingleReferenceWithLatest(locale: string, formFactor: string) {
const resourceToUpdate = this.webviewSnapshot.resources.find(
(resource) => resource.locale === locale && resource.formFactor === formFactor
);

if (!resourceToUpdate) {
window.showErrorMessage(
`${this.webviewSnapshot.name} snapshot for locale ${locale} and form factor ${formFactor} does not exist.`
);
return;
}
const singleResourceSnapshot = new WdioSnapshot(this.webviewSnapshot.name, 'dummyParentId');
singleResourceSnapshot.addResource(resourceToUpdate);
replaceReferenceWithLatest(singleResourceSnapshot);
}
}

export default WdioWebviewPanel;
2 changes: 1 addition & 1 deletion tests/unit/webview-ui/App.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('App', () => {
it('should send a webview ready message when the app is mounted', () => {
render(App);
expect(vscode.postMessage).toHaveBeenCalledTimes(1);
expect(vscode.postMessage).toHaveBeenCalledWith({ ready: true });
expect(vscode.postMessage).toHaveBeenCalledWith({ intent: 'webviewReady', ready: true });
});

it('should set the scroll position to the saved vscode state when it exists', async () => {
Expand Down
19 changes: 19 additions & 0 deletions tests/unit/webview-ui/snapshot/SnapshotContainer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ describe('SnapshotContainer', () => {

beforeEach(() => {
mockGetContext.mockReturnValue(extensionConfig);
vscode.postMessage.mockClear();
vscode.setState.mockClear();
});

it('should render a tab for each resource', () => {
Expand Down Expand Up @@ -104,4 +106,21 @@ describe('SnapshotContainer', () => {
await fireEvent.click(screen.getByRole('tab', { name: 'latest' }));
expect(vscode.setState).toHaveBeenLastCalledWith({ [`${containerId}-active-tab`]: 'latest' });
});

it('should send a message to replace the reference snapshot with latest when the replace button is clicked', () => {
const resource = buildResource({ diff: true });
render(SnapshotContainer, { resource });
fireEvent.click(screen.getByRole('button', { name: 'Replace reference with latest' }));
expect(vscode.postMessage).toHaveBeenCalledTimes(1);
expect(vscode.postMessage).toHaveBeenCalledWith({
intent: 'replaceReferenceWithLatest',
locale: resource.locale,
formFactor: resource.formFactor,
});
});

it('should render the replace button as disabled when there is no diff snapshot present', () => {
render(SnapshotContainer, { resource: buildResource({ diff: false }) });
expect(screen.getByRole('button', { name: 'Replace reference with latest' })).toBeDisabled();
});
});
4 changes: 2 additions & 2 deletions tests/unit/webview-ui/vscodeWebviewApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ describe('vscode webview api', () => {

describe('sendWebviewMessage', () => {
it('should post a vscode webview message with the provided message', () => {
sendWebviewMessage('some message');
sendWebviewMessage({ intent: 'webviewReady', ready: true });
expect(mockVsCodeApi.postMessage).toHaveBeenCalledTimes(1);
expect(mockVsCodeApi.postMessage).toHaveBeenCalledWith('some message');
expect(mockVsCodeApi.postMessage).toHaveBeenCalledWith({ intent: 'webviewReady', ready: true });
});
});
});
Loading

0 comments on commit 38ebffd

Please sign in to comment.