Add Windows save instructions UI and fix URL #296
- Add Windows instruction dialog when saving scripts for Windows. - Fix incorrect macOS download URL given for Linux instructions. - Refactor UI rendering, eleminating the use of `v-html` and JavaScript variables to hold HTML code.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import CodeInstruction from '@/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/CopyableCommand.vue';
|
||||
import { expectThrowsAsync } from '@tests/shared/Assertions/ExpectThrowsAsync';
|
||||
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
||||
import { Clipboard } from '@/presentation/components/Shared/Hooks/Clipboard/Clipboard';
|
||||
import { UseClipboardStub } from '@tests/unit/shared/Stubs/UseClipboardStub';
|
||||
import { ClipboardStub } from '@tests/unit/shared/Stubs/ClipboardStub';
|
||||
import { expectExists } from '@tests/shared/Assertions/ExpectExists';
|
||||
import FlatButton from '@/presentation/components/Shared/FlatButton.vue';
|
||||
|
||||
const DOM_SELECTOR_CODE_SLOT = 'code';
|
||||
const COMPONENT_TOOLTIP_WRAPPER_NAME = 'TooltipWrapper';
|
||||
|
||||
describe('CodeInstruction.vue', () => {
|
||||
it('renders a slot content inside a <code> element', () => {
|
||||
// arrange
|
||||
const expectedSlotContent = 'Example Code';
|
||||
const wrapper = mountComponent({
|
||||
slotContent: expectedSlotContent,
|
||||
});
|
||||
// act
|
||||
const codeSlot = wrapper.find(DOM_SELECTOR_CODE_SLOT);
|
||||
const actualContent = codeSlot.text();
|
||||
// assert
|
||||
expect(actualContent).to.equal(expectedSlotContent);
|
||||
});
|
||||
describe('copy', () => {
|
||||
it('calls copyText when the copy button is clicked', async () => {
|
||||
// arrange
|
||||
const expectedCode = 'Code to be copied';
|
||||
const clipboardStub = new ClipboardStub();
|
||||
const wrapper = mountComponent({
|
||||
clipboard: clipboardStub,
|
||||
});
|
||||
wrapper.vm.codeElement = { textContent: expectedCode } as HTMLElement;
|
||||
// act
|
||||
const copyButton = wrapper.findComponent(FlatButton);
|
||||
await copyButton.trigger('click');
|
||||
// assert
|
||||
const calls = clipboardStub.callHistory;
|
||||
expect(calls).to.have.lengthOf(1);
|
||||
const call = calls.find((c) => c.methodName === 'copyText');
|
||||
expectExists(call);
|
||||
const [actualCode] = call.args;
|
||||
expect(actualCode).to.equal(expectedCode);
|
||||
});
|
||||
it('throws an error when codeElement is not found during copy', async () => {
|
||||
// arrange
|
||||
const expectedError = 'Code element could not be found.';
|
||||
const wrapper = mountComponent();
|
||||
wrapper.vm.codeElement = undefined;
|
||||
// act
|
||||
const act = () => wrapper.vm.copyCode();
|
||||
// assert
|
||||
await expectThrowsAsync(act, expectedError);
|
||||
});
|
||||
it('throws an error when codeElement has no textContent during copy', async () => {
|
||||
// arrange
|
||||
const expectedError = 'Code element does not contain any text.';
|
||||
const wrapper = mountComponent();
|
||||
wrapper.vm.codeElement = { textContent: '' } as HTMLElement;
|
||||
// act
|
||||
const act = () => wrapper.vm.copyCode();
|
||||
// assert
|
||||
await expectThrowsAsync(act, expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function mountComponent(options?: {
|
||||
readonly clipboard?: Clipboard,
|
||||
readonly slotContent?: string,
|
||||
}) {
|
||||
return shallowMount(CodeInstruction, {
|
||||
global: {
|
||||
provide: {
|
||||
[InjectionKeys.useClipboard.key]:
|
||||
() => {
|
||||
if (options?.clipboard) {
|
||||
return new UseClipboardStub(options.clipboard).get();
|
||||
}
|
||||
return new UseClipboardStub().get();
|
||||
},
|
||||
},
|
||||
stubs: {
|
||||
[COMPONENT_TOOLTIP_WRAPPER_NAME]: {
|
||||
name: COMPONENT_TOOLTIP_WRAPPER_NAME,
|
||||
template: '<slot />',
|
||||
},
|
||||
},
|
||||
},
|
||||
slots: {
|
||||
default: options?.slotContent ?? 'Stubbed slot content',
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import PlatformInstructionSteps from '@/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/PlatformInstructionSteps.vue';
|
||||
import { useCollectionState } from '@/presentation/components/Shared/Hooks/UseCollectionState';
|
||||
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
||||
import { UseCollectionStateStub } from '@tests/unit/shared/Stubs/UseCollectionStateStub';
|
||||
import { AllSupportedOperatingSystems, SupportedOperatingSystem } from '@tests/shared/TestCases/SupportedOperatingSystems';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { CategoryCollectionStateStub } from '@tests/unit/shared/Stubs/CategoryCollectionStateStub';
|
||||
import WindowsInstructions from '@/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/WindowsInstructions.vue';
|
||||
import MacOsInstructions from '@/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/MacOsInstructions.vue';
|
||||
import LinuxInstructions from '@/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/LinuxInstructions.vue';
|
||||
import type { Component } from 'vue';
|
||||
|
||||
describe('PlatformInstructionSteps', () => {
|
||||
const testScenarios: Record<SupportedOperatingSystem, Component> = {
|
||||
[OperatingSystem.Windows]: WindowsInstructions,
|
||||
[OperatingSystem.macOS]: MacOsInstructions,
|
||||
[OperatingSystem.Linux]: LinuxInstructions,
|
||||
};
|
||||
AllSupportedOperatingSystems.forEach((operatingSystem) => {
|
||||
it(`renders the correct component for ${OperatingSystem[operatingSystem]}`, () => {
|
||||
// arrange
|
||||
const expectedComponent = testScenarios[operatingSystem];
|
||||
const useCollectionStateStub = new UseCollectionStateStub()
|
||||
.withState(new CategoryCollectionStateStub().withOs(operatingSystem));
|
||||
|
||||
// act
|
||||
const wrapper = mountComponent({
|
||||
useCollectionState: useCollectionStateStub.get(),
|
||||
});
|
||||
|
||||
// assert
|
||||
expect(wrapper.findComponent(expectedComponent).exists()).to.equal(true);
|
||||
});
|
||||
it(`binds the correct filename for ${OperatingSystem[operatingSystem]}`, () => {
|
||||
// arrange
|
||||
const expectedFilename = 'expected-file-name.bat';
|
||||
const wrappedComponent = testScenarios[operatingSystem];
|
||||
const useCollectionStateStub = new UseCollectionStateStub()
|
||||
.withState(new CategoryCollectionStateStub().withOs(operatingSystem));
|
||||
|
||||
// act
|
||||
const wrapper = mountComponent({
|
||||
useCollectionState: useCollectionStateStub.get(),
|
||||
filename: expectedFilename,
|
||||
});
|
||||
|
||||
// assert
|
||||
const componentWrapper = wrapper.findComponent(wrappedComponent);
|
||||
expect(componentWrapper.props('filename')).to.equal(expectedFilename);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function mountComponent(options?: {
|
||||
readonly useCollectionState?: ReturnType<typeof useCollectionState>;
|
||||
readonly filename?: string;
|
||||
}) {
|
||||
return shallowMount(PlatformInstructionSteps, {
|
||||
global: {
|
||||
provide: {
|
||||
[InjectionKeys.useCollectionState.key]:
|
||||
() => options?.useCollectionState ?? new UseCollectionStateStub().get(),
|
||||
},
|
||||
},
|
||||
props: {
|
||||
filename: options?.filename === undefined ? 'privacy-test-script.bat' : options.filename,
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user