Skip to content

Commit

Permalink
Remove experimental warning from deploy pane, add tests (#16173)
Browse files Browse the repository at this point in the history
As previously discussed, the only reason to maintain the "experimental"
warning in the deploy pane was to ensure that the feature was fully
tested before being released. Thanks to @shenglol's refactoring, I'm now
able to add react tests for the frontend logic.

###### Microsoft Reviewers: [Open in
CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/Azure/bicep/pull/16173)
  • Loading branch information
anthony-c-martin authored Jan 24, 2025
1 parent c64428c commit 8ae4324
Show file tree
Hide file tree
Showing 18 changed files with 2,301 additions and 459 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ jobs:
run: npm run lint
working-directory: ./src/vscode-bicep-ui

- name: Run build
run: npm run build
working-directory: ./src/vscode-bicep-ui

- name: Run tests
run: npm run test
working-directory: ./src/vscode-bicep-ui
Expand Down
12 changes: 11 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@
"request": "launch",
"script": "${file}",
"cwd": "${cwd}"
}
},
{
"name": "Deploy Pane Tests",
"type": "node",
"request": "launch",
"runtimeExecutable": "npm",
"runtimeArgs": [
"test"
],
"cwd": "${workspaceFolder}/src/vscode-bicep-ui/apps/deploy-pane"
},
]
}
13 changes: 12 additions & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,20 @@
"cwd": "${workspaceFolder}/src/vscode-bicep"
},
"dependsOn": [
"Build Language Server for VSIX"
"Build Language Server for VSIX",
"Build VSCode UI"
]
},
{
"label": "Build VSCode UI",
"command": "npm",
"args": ["run", "build", "--loglevel", "silent"],
"type": "shell",
"problemMatcher": "$tsc-watch",
"options": {
"cwd": "${workspaceFolder}/src/vscode-bicep-ui"
}
},
{
"label": "Build WASM for Playground",
"command": "npm",
Expand Down
14 changes: 12 additions & 2 deletions src/vscode-bicep-ui/apps/deploy-pane/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,33 @@
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest",
"lint": "eslint . --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint . --fix"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^16.0.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.6.0",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^3.0.2",
"@vscode-elements/webview-playground": "^1.3.0",
"vite": "^5.4.5",
"vite-plugin-ejs": "^1.7.0"
"element-internals-polyfill": "^1.3.12",
"jsdom": "^26.0.0",
"typescript": "^5.7.3",
"vite": "^5.4.8",
"vite-plugin-ejs": "^1.7.0",
"vitest": "^3.0.2"
},
"dependencies": {
"@azure/arm-resources": "^5.2.0",
"@azure/identity": "^4.5.0",
"@microsoft/vscode-azext-utils": "^2.5.12",
"@vscode-bicep-ui/components": "^0.0.0",
"@vscode-elements/elements": "^1.11.0",
"@vscode-elements/react-elements": "^0.8.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
Expand Down
163 changes: 163 additions & 0 deletions src/vscode-bicep-ui/apps/deploy-pane/src/__tests__/App.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { beforeEach, describe, expect, it, vi } from 'vitest'
import { render, waitFor, fireEvent, screen, act } from '@testing-library/react'
import { App } from '../components/App'
import { vscode } from '../vscode'
import { createDeploymentDataMessage, createGetAccessTokenMessage, createGetAccessTokenResultMessage, createGetDeploymentScopeMessage, createGetDeploymentScopeResultMessage, createGetStateMessage, createGetStateResultMessage, createReadyMessage, type VscodeMessage } from '../messages';
import { fileUri, getDeploymentOperations, getDeployResponse, getValidateResponse, getWhatIfResponse, parametersJson, scope, templateJson } from './mockData';

const mockClient = {
deployments: {
beginCreateOrUpdateAtScope: vi.fn(async () => {
return {
isDone: vi.fn(() => true),
getResult: vi.fn(getDeployResponse),
};
}),
beginValidateAtScopeAndWait: vi.fn(async () => getValidateResponse()),
beginWhatIfAndWait: vi.fn(async () => getWhatIfResponse()),
},
deploymentOperations: {
listAtScope: vi.fn(getDeploymentOperations),
}
}

beforeEach(() => {
vi.mock('@azure/arm-resources', async (importOriginal) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mod: any = await importOriginal();
return {
...mod,
ResourceManagementClient: vi.fn(() => mockClient),
};
})
vi.mock('../components/hooks/time', () => ({
getDate: () => '1737601964200',
}));
});

describe('App', () => {
it('renders the loading spinner before initialization', async () => {
const { container } = render(<App />);
expect(container).toMatchSnapshot();
});

it('renders the App component with deployment state', async () => {
const { container } = render(<App />);

await initialize();

expect(container).toMatchSnapshot();
});

it('runs a deployment', async () => {
const { container } = render(<App />);

const scope = await initialize();

const deployButton = screen.getByText('Deploy');
fireEvent.click(deployButton);

await act(async () => {
await waitFor(() => expect(vscode.postMessage).toBeCalledWith(createGetAccessTokenMessage(scope)));
sendMessage(getAccessTokenResultMessage());
})

expect(container).toMatchSnapshot();
});

it('validates a deployment', async () => {
const { container } = render(<App />);

const scope = await initialize();

const validateButton = screen.getByText('Validate');
fireEvent.click(validateButton);

await act(async () => {
await waitFor(() => expect(vscode.postMessage).toBeCalledWith(createGetAccessTokenMessage(scope)));
sendMessage(getAccessTokenResultMessage());
})

expect(container).toMatchSnapshot();
});

it('what-ifs a deployment', async () => {
const { container } = render(<App />);

const scope = await initialize();

const whatIfButton = screen.getByText('What-If');
fireEvent.click(whatIfButton);

await act(async () => {
await waitFor(() => expect(vscode.postMessage).toBeCalledWith(createGetAccessTokenMessage(scope)));
sendMessage(getAccessTokenResultMessage());
})

expect(container).toMatchSnapshot();
});

it('supports the scope picker', async () => {
const { container } = render(<App />);

const scope = await initialize();

const changeScopeButton = screen.getByText('Change Scope');
fireEvent.click(changeScopeButton);

await act(async () => {
await waitFor(() => expect(vscode.postMessage).toBeCalledWith(createGetDeploymentScopeMessage("resourceGroup")));
sendMessage(createGetDeploymentScopeResultMessage({
...scope,
tenantId: 'newTenantId',
subscriptionId: 'newSubscriptionId',
resourceGroup: 'newResourceGroup',
}));
})

expect(container).toMatchSnapshot();
});
})

async function initialize() {
const stateResult = getStateResultMessage();
await act(async () => {
await waitFor(() => expect(vscode.postMessage).toBeCalledWith(createReadyMessage()));
sendMessage(getDeploymentDataMessage());

await waitFor(() => expect(vscode.postMessage).toBeCalledWith(createGetStateMessage()));
sendMessage(stateResult);
})

return scope;
}

function sendMessage(message: VscodeMessage) {
fireEvent(
window,
new MessageEvent<VscodeMessage>("message", { data: message }));
}

function getDeploymentDataMessage() {
return createDeploymentDataMessage(
fileUri,
false,
templateJson,
parametersJson);
}

function getStateResultMessage() {
return createGetStateResultMessage({
scope: scope,
});
}

function getAccessTokenResultMessage() {
return createGetAccessTokenResultMessage({
expiresOnTimestamp: 1737601964200,
token: 'mockAccessToken'
});
}
Loading

0 comments on commit 8ae4324

Please sign in to comment.