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:
@@ -137,7 +137,7 @@ For a detailed comparison of features between the desktop and web versions of pr
|
||||
- **Transparent**. Have full visibility into what the tweaks do as you enable them.
|
||||
- **Reversible**. Revert if something feels wrong.
|
||||
- **Accessible**. No need to run any compiled software on your computer with web version.
|
||||
- **Secure**: Security is a top priority at privacy.sexy with [comprehensive safeguards](./SECURITY.md#application-security) in place.
|
||||
- **Secure**: Security is a top priority at privacy.sexy with [comprehensive safeguards](./SECURITY.md#security-practices) in place.
|
||||
- **Open**. What you see as code in this repository is what you get. The application itself, its infrastructure and deployments are open-source and automated thanks to [bump-everywhere](https://github.com/undergroundwires/bump-everywhere).
|
||||
- **Tested**. A lot of tests. Automated and manual. Community-testing and verification. Stability improvements comes before new features.
|
||||
- **Extensible**. Effortlessly [extend scripts](./CONTRIBUTING.md#extend-scripts) with a custom designed [templating language](./docs/templating.md).
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
:icon-name="isRunningAsDesktopApplication ? 'floppy-disk' : 'file-arrow-down'"
|
||||
@click="saveCode"
|
||||
/>
|
||||
<ModalDialog v-if="instructions" v-model="areInstructionsVisible">
|
||||
<InstructionList :data="instructions" />
|
||||
<ModalDialog v-model="areInstructionsVisible">
|
||||
<RunInstructions :filename="filename" />
|
||||
</ModalDialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -22,14 +22,12 @@ import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
||||
import { ScriptFilename } from '@/application/CodeRunner/ScriptFilename';
|
||||
import { Dialog, FileType } from '@/presentation/common/Dialog';
|
||||
import IconButton from '../IconButton.vue';
|
||||
import InstructionList from './Instructions/InstructionList.vue';
|
||||
import { IInstructionListData } from './Instructions/InstructionListData';
|
||||
import { getInstructions } from './Instructions/InstructionListDataFactory';
|
||||
import RunInstructions from './RunInstructions/RunInstructions.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
IconButton,
|
||||
InstructionList,
|
||||
RunInstructions,
|
||||
ModalDialog,
|
||||
},
|
||||
setup() {
|
||||
@@ -39,10 +37,6 @@ export default defineComponent({
|
||||
|
||||
const areInstructionsVisible = ref(false);
|
||||
const filename = computed<string>(() => buildFilename(currentState.value.collection.scripting));
|
||||
const instructions = computed<IInstructionListData | undefined>(() => getInstructions(
|
||||
currentState.value.collection.os,
|
||||
filename.value,
|
||||
));
|
||||
|
||||
async function saveCode() {
|
||||
const { success, error } = await dialog.saveFile(
|
||||
@@ -59,8 +53,8 @@ export default defineComponent({
|
||||
|
||||
return {
|
||||
isRunningAsDesktopApplication,
|
||||
instructions,
|
||||
areInstructionsVisible,
|
||||
filename,
|
||||
saveCode,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { IInstructionListData, IInstructionListStep } from '../InstructionListData';
|
||||
|
||||
export interface IInstructionsBuilderData {
|
||||
readonly fileName: string;
|
||||
}
|
||||
|
||||
export type InstructionStepBuilderType = (data: IInstructionsBuilderData) => IInstructionListStep;
|
||||
|
||||
export class InstructionsBuilder {
|
||||
private readonly stepBuilders = new Array<InstructionStepBuilderType>();
|
||||
|
||||
constructor(private readonly os: OperatingSystem) {
|
||||
|
||||
}
|
||||
|
||||
public withStep(stepBuilder: InstructionStepBuilderType) {
|
||||
this.stepBuilders.push(stepBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
public build(data: IInstructionsBuilderData): IInstructionListData {
|
||||
return {
|
||||
operatingSystem: this.os,
|
||||
steps: this.stepBuilders.map((stepBuilder) => stepBuilder(data)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { InstructionsBuilder } from './InstructionsBuilder';
|
||||
|
||||
export class LinuxInstructionsBuilder extends InstructionsBuilder {
|
||||
constructor() {
|
||||
super(OperatingSystem.Linux);
|
||||
super
|
||||
.withStep(() => ({
|
||||
action: {
|
||||
instruction: 'Download the file.',
|
||||
details: 'You should have already been prompted to save the script file.'
|
||||
+ '<br/>If this was not the case or you did not save the script when prompted,'
|
||||
+ '<br/>please try to download your script file again.',
|
||||
},
|
||||
}))
|
||||
.withStep(() => ({
|
||||
action: {
|
||||
instruction: 'Open terminal.',
|
||||
details:
|
||||
'Opening terminal changes based on the distro you run.'
|
||||
+ '<br/>You may search for "Terminal" in your application launcher to find it.'
|
||||
+ '<br/>'
|
||||
+ '<br/>Alternatively use terminal shortcut for your distro if it has one by default:'
|
||||
+ '<ul>'
|
||||
+ '<li><code>Ctrl-Alt-T</code>: Ubuntu, CentOS, Linux Mint, Elementary OS, ubermix, Kali…</li>'
|
||||
+ '<li><code>Super-T</code>: Pop!_OS…</li>'
|
||||
+ '<li><code>Alt-T</code>: Parrot OS…</li>'
|
||||
+ '<li><code>Ctrl-Alt-Insert</code>: Bodhi Linux…</li>'
|
||||
+ '</ul>'
|
||||
,
|
||||
},
|
||||
}))
|
||||
.withStep(() => ({
|
||||
action: {
|
||||
instruction: 'Navigate to the folder where you downloaded the file e.g.:',
|
||||
},
|
||||
code: {
|
||||
instruction: 'cd ~/Downloads',
|
||||
details: 'Press on <code>enter/return</code> key after running the command.'
|
||||
+ '<br/>If the file is not downloaded on Downloads folder,'
|
||||
+ '<br/>change <code>Downloads</code> to path where the file is downloaded.'
|
||||
+ '<br/>'
|
||||
+ '<br/>This command means:'
|
||||
+ '<ul>'
|
||||
+ '<li><code>cd</code> will change the current folder.</li>'
|
||||
+ '<li><code>~</code> is the user home directory.</li>'
|
||||
+ '</ul>',
|
||||
},
|
||||
}))
|
||||
.withStep((data) => ({
|
||||
action: {
|
||||
instruction: 'Give the file execute permissions:',
|
||||
},
|
||||
code: {
|
||||
instruction: `chmod +x ${data.fileName}`,
|
||||
details: 'Press on <code>enter/return</code> key after running the command.<br/>'
|
||||
+ 'It will make the file executable. <br/>'
|
||||
+ 'If you use desktop environment you can alternatively (instead of running the command):'
|
||||
+ '<ol>'
|
||||
+ '<li>Locate the file using your file manager.</li>'
|
||||
+ '<li>Right click on the file, select "Properties".</li>'
|
||||
+ '<li>Go to "Permissions" and check "Allow executing file as program".</li>'
|
||||
+ '</ol>'
|
||||
+ '<br/>These GUI steps and name of options may change depending on your file manager.'
|
||||
,
|
||||
},
|
||||
}))
|
||||
.withStep((data) => ({
|
||||
action: {
|
||||
instruction: 'Execute the file:',
|
||||
},
|
||||
code: {
|
||||
instruction: `./${data.fileName}`,
|
||||
details:
|
||||
'If you have desktop environment, instead of running this command you can alternatively:'
|
||||
+ '<ol>'
|
||||
+ '<li>Locate the file using your file manager.</li>'
|
||||
+ '<li>Right click on the file, select "Run as program".</li>'
|
||||
+ '</ol>'
|
||||
,
|
||||
},
|
||||
}))
|
||||
.withStep(() => ({
|
||||
action: {
|
||||
instruction: 'If asked, enter your administrator password.',
|
||||
details: 'As you type, your password will be hidden but the keys are still registered, so keep typing.'
|
||||
+ '<br/>Press on <code>enter/return</code> key after typing your password.'
|
||||
+ '<br/>Administrator privileges are required to configure OS.',
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { InstructionsBuilder } from './InstructionsBuilder';
|
||||
|
||||
export class MacOsInstructionsBuilder extends InstructionsBuilder {
|
||||
constructor() {
|
||||
super(OperatingSystem.macOS);
|
||||
super
|
||||
.withStep(() => ({
|
||||
action: {
|
||||
instruction: 'Download the file.',
|
||||
details: 'You should have already been prompted to save the script file.'
|
||||
+ '<br/>If this was not the case or you did not save the script when prompted,'
|
||||
+ '<br/>please try to download your script file again.'
|
||||
,
|
||||
},
|
||||
}))
|
||||
.withStep(() => ({
|
||||
action: {
|
||||
instruction: 'Open terminal.',
|
||||
details: 'Type Terminal into Spotlight or open it from the Applications -> Utilities folder.',
|
||||
},
|
||||
}))
|
||||
.withStep(() => ({
|
||||
action: {
|
||||
instruction: 'Navigate to the folder where you downloaded the file e.g.:',
|
||||
},
|
||||
code: {
|
||||
instruction: 'cd ~/Downloads',
|
||||
details: 'Press on <code>enter/return</code> key after running the command.'
|
||||
+ '<br/>If the file is not downloaded on Downloads folder,'
|
||||
+ '<br/>change <code>Downloads</code> to path where the file is downloaded.'
|
||||
+ '<br/>'
|
||||
+ '<br/>This command means:'
|
||||
+ '<ul>'
|
||||
+ '<li><code>cd</code> will change the current folder.</li>'
|
||||
+ '<li><code>~</code> is the user home directory.</li>'
|
||||
+ '</ul>',
|
||||
},
|
||||
}))
|
||||
.withStep((data) => ({
|
||||
action: {
|
||||
instruction: 'Give the file execute permissions:',
|
||||
},
|
||||
code: {
|
||||
instruction: `chmod +x ${data.fileName}`,
|
||||
details: 'Press on <code>enter/return</code> key after running the command.<br/>'
|
||||
+ 'It will make the file executable.'
|
||||
,
|
||||
},
|
||||
}))
|
||||
.withStep((data) => ({
|
||||
action: {
|
||||
instruction: 'Execute the file:',
|
||||
},
|
||||
code: {
|
||||
instruction: `./${data.fileName}`,
|
||||
details: 'Alternatively you can locate the file in <strong>Finder</strong> and double click on it.',
|
||||
},
|
||||
}))
|
||||
.withStep(() => ({
|
||||
action: {
|
||||
instruction: 'If asked, enter your administrator password.',
|
||||
details: 'As you type, your password will be hidden but the keys are still registered, so keep typing.'
|
||||
+ '<br/>Press on <code>enter/return</code> key after typing your password.'
|
||||
+ '<br/>Administrator privileges are required to configure OS.'
|
||||
,
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<template>
|
||||
<TooltipWrapper>
|
||||
<AppIcon icon="circle-info" />
|
||||
<template #tooltip>
|
||||
<slot />
|
||||
</template>
|
||||
</TooltipWrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue';
|
||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
TooltipWrapper,
|
||||
AppIcon,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -1,119 +0,0 @@
|
||||
<template>
|
||||
<div class="instructions">
|
||||
<p>
|
||||
You have two alternatives to apply your selection.
|
||||
</p>
|
||||
<hr />
|
||||
<p>
|
||||
<strong>1. The easy alternative</strong>. Run your script without any manual steps by
|
||||
<a :href="macOsDownloadUrl">downloading desktop version</a> of {{ appName }} on the
|
||||
{{ osName }} system you wish to configure, and then click on the Run button. This is
|
||||
recommended for most users.
|
||||
</p>
|
||||
<hr />
|
||||
<p>
|
||||
<strong>2. The hard (manual) alternative</strong>. This requires you to do additional manual
|
||||
steps. If you are unsure how to follow the instructions, tap or hover on information
|
||||
(<InfoTooltip>Engage with icons like this for extra wisdom!</InfoTooltip>)
|
||||
icons near the steps, or follow the easy alternative described above.
|
||||
</p>
|
||||
<p>
|
||||
<ol>
|
||||
<li
|
||||
v-for="(step, index) in data.steps"
|
||||
:key="index"
|
||||
class="step"
|
||||
>
|
||||
<div class="step__action">
|
||||
<span>{{ step.action.instruction }}</span>
|
||||
<div v-if="step.action.details" class="details-container">
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<InfoTooltip><div v-html="step.action.details" /></InfoTooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="step.code" class="step__code">
|
||||
<CodeInstruction>{{ step.code.instruction }}</CodeInstruction>
|
||||
<div v-if="step.code.details" class="details-container">
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<InfoTooltip><div v-html="step.code.details" /></InfoTooltip>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent, PropType, computed,
|
||||
} from 'vue';
|
||||
import { injectKey } from '@/presentation/injectionSymbols';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import CodeInstruction from './CodeInstruction.vue';
|
||||
import { IInstructionListData } from './InstructionListData';
|
||||
import InfoTooltip from './InfoTooltip.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
CodeInstruction,
|
||||
InfoTooltip,
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
type: Object as PropType<IInstructionListData>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { info } = injectKey((keys) => keys.useApplication);
|
||||
|
||||
const appName = computed<string>(() => info.name);
|
||||
|
||||
const macOsDownloadUrl = computed<string>(
|
||||
() => info.getDownloadUrl(OperatingSystem.macOS),
|
||||
);
|
||||
|
||||
const osName = computed<string>(
|
||||
() => renderOsName(props.data.operatingSystem),
|
||||
);
|
||||
|
||||
return {
|
||||
appName,
|
||||
macOsDownloadUrl,
|
||||
osName,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
function renderOsName(os: OperatingSystem): string {
|
||||
switch (os) {
|
||||
case OperatingSystem.Windows: return 'Windows';
|
||||
case OperatingSystem.macOS: return 'macOS';
|
||||
case OperatingSystem.Linux: return 'Linux';
|
||||
default: throw new RangeError(`Cannot render os name: ${OperatingSystem[os]}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "@/presentation/assets/styles/main" as *;
|
||||
|
||||
.step {
|
||||
margin: 10px 0;
|
||||
&__action {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
&__code {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
.details-container {
|
||||
margin-left: 0.5em; // Do not style icon itself to ensure correct tooltip alignment
|
||||
}
|
||||
</style>
|
||||
@@ -1,16 +0,0 @@
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
export interface IInstructionListData {
|
||||
readonly operatingSystem: OperatingSystem;
|
||||
readonly steps: readonly IInstructionListStep[];
|
||||
}
|
||||
|
||||
export interface IInstructionListStep {
|
||||
readonly action: IInstructionInfo;
|
||||
readonly code?: IInstructionInfo;
|
||||
}
|
||||
|
||||
export interface IInstructionInfo {
|
||||
readonly instruction: string;
|
||||
readonly details?: string;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { InstructionsBuilder } from './Data/InstructionsBuilder';
|
||||
import { MacOsInstructionsBuilder } from './Data/MacOsInstructionsBuilder';
|
||||
import { IInstructionListData } from './InstructionListData';
|
||||
import { LinuxInstructionsBuilder } from './Data/LinuxInstructionsBuilder';
|
||||
|
||||
const builders = new Map<OperatingSystem, InstructionsBuilder>([
|
||||
[OperatingSystem.macOS, new MacOsInstructionsBuilder()],
|
||||
[OperatingSystem.Linux, new LinuxInstructionsBuilder()],
|
||||
]);
|
||||
|
||||
export function getInstructions(
|
||||
os: OperatingSystem,
|
||||
fileName: string,
|
||||
): IInstructionListData | undefined {
|
||||
return builders
|
||||
.get(os)
|
||||
?.build({ fileName });
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div class="info-container">
|
||||
<TooltipWrapper>
|
||||
<AppIcon icon="circle-info" />
|
||||
<template #tooltip>
|
||||
<slot />
|
||||
</template>
|
||||
</TooltipWrapper>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue';
|
||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
TooltipWrapper,
|
||||
AppIcon,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.info-container {
|
||||
display: inline-block;
|
||||
margin-left: 0.15em; // Do not style icon itself to ensure correct tooltip alignment
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="instructions">
|
||||
<p>
|
||||
You have two alternatives to apply your selection.
|
||||
</p>
|
||||
<hr />
|
||||
<p>
|
||||
<strong>1. The easy alternative</strong>. Run your script without any manual steps by
|
||||
<a :href="downloadUrl">downloading desktop version</a> of {{ appName }} on the
|
||||
{{ osName }} system you wish to configure, and then click on the Run button. This is
|
||||
recommended for most users.
|
||||
</p>
|
||||
<hr />
|
||||
<p>
|
||||
<strong>2. The hard (manual) alternative</strong>. This requires you to do additional manual
|
||||
steps. If you are unsure how to follow the instructions, tap or hover on information
|
||||
<InfoTooltip>Engage with icons like this for extra wisdom!</InfoTooltip>
|
||||
icons near the steps, or follow the easy alternative described above.
|
||||
</p>
|
||||
<p>
|
||||
<PlatformInstructionSteps :filename="filename" />
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { injectKey } from '@/presentation/injectionSymbols';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import InfoTooltip from './InfoTooltip.vue';
|
||||
import PlatformInstructionSteps from './Steps/PlatformInstructionSteps.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
InfoTooltip,
|
||||
PlatformInstructionSteps,
|
||||
},
|
||||
props: {
|
||||
filename: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { currentState } = injectKey((keys) => keys.useCollectionState);
|
||||
|
||||
const { info } = injectKey((keys) => keys.useApplication);
|
||||
|
||||
const operatingSystem = computed<OperatingSystem>(() => currentState.value.os);
|
||||
|
||||
const appName = computed<string>(() => info.name);
|
||||
|
||||
const downloadUrl = computed<string>(
|
||||
() => info.getDownloadUrl(operatingSystem.value),
|
||||
);
|
||||
|
||||
const osName = computed<string>(
|
||||
() => renderOsName(operatingSystem.value),
|
||||
);
|
||||
|
||||
return {
|
||||
appName,
|
||||
downloadUrl,
|
||||
osName,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
function renderOsName(os: OperatingSystem): string {
|
||||
switch (os) {
|
||||
case OperatingSystem.Windows: return 'Windows';
|
||||
case OperatingSystem.macOS: return 'macOS';
|
||||
case OperatingSystem.Linux: return 'Linux';
|
||||
default: throw new RangeError(`Cannot render os name: ${OperatingSystem[os]}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "@/presentation/assets/styles/main" as *;
|
||||
|
||||
.step {
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -27,10 +27,10 @@ export default defineComponent({
|
||||
setup() {
|
||||
const { copyText } = injectKey((keys) => keys.useClipboard);
|
||||
|
||||
const codeElement = shallowRef<HTMLElement | undefined>();
|
||||
const codeElementRef = shallowRef<HTMLElement | undefined>();
|
||||
|
||||
async function copyCode() {
|
||||
const element = codeElement.value;
|
||||
const element = codeElementRef.value;
|
||||
if (!element) {
|
||||
throw new Error('Code element could not be found.');
|
||||
}
|
||||
@@ -43,7 +43,7 @@ export default defineComponent({
|
||||
|
||||
return {
|
||||
copyCode,
|
||||
codeElement,
|
||||
codeElement: codeElementRef,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -53,7 +53,7 @@ export default defineComponent({
|
||||
@use "@/presentation/assets/styles/main" as *;
|
||||
|
||||
.code-wrapper {
|
||||
display:flex;
|
||||
display: inline-flex;
|
||||
white-space: nowrap;
|
||||
justify-content: space-between;
|
||||
font-family: $font-normal;
|
||||
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<li class="step">
|
||||
<slot />
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'InstructionSteps', // Define component name for empty component for Vue build and ESLint compatibility.
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.step {
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<ol>
|
||||
<slot />
|
||||
</ol>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'InstructionSteps', // Define component name for empty component for Vue build and ESLint compatibility.
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<component
|
||||
:is="component"
|
||||
v-if="component"
|
||||
:filename="filename"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { injectKey } from '@/presentation/injectionSymbols';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import MacOsInstructions from './Platforms/MacOsInstructions.vue';
|
||||
import LinuxInstructions from './Platforms/LinuxInstructions.vue';
|
||||
import WindowsInstructions from './Platforms/WindowsInstructions.vue';
|
||||
import type { Component } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
filename: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { currentState } = injectKey((keys) => keys.useCollectionState);
|
||||
|
||||
const component = computed<Component>(() => getInstructionsComponent(
|
||||
currentState.value.collection.os,
|
||||
));
|
||||
|
||||
return {
|
||||
component,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
function getInstructionsComponent(operatingSystem: OperatingSystem): Component {
|
||||
switch (operatingSystem) {
|
||||
case OperatingSystem.macOS:
|
||||
return MacOsInstructions;
|
||||
case OperatingSystem.Linux:
|
||||
return LinuxInstructions;
|
||||
case OperatingSystem.Windows:
|
||||
return WindowsInstructions;
|
||||
default:
|
||||
throw new Error(`No instructions for the operating system: ${OperatingSystem[operatingSystem]}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<InstructionSteps>
|
||||
<InstructionStep>
|
||||
Download the file.
|
||||
<InfoTooltip>
|
||||
<p>
|
||||
You should have already been prompted to save the script file.
|
||||
</p>
|
||||
<p>
|
||||
If this was not the case or you did not save the script when prompted,
|
||||
please try to download your script file again.
|
||||
</p>
|
||||
</InfoTooltip>
|
||||
</InstructionStep>
|
||||
<InstructionStep>
|
||||
Open terminal.
|
||||
<InfoTooltip>
|
||||
<p>
|
||||
Opening terminal changes based on the distro you run.
|
||||
</p>
|
||||
<p>
|
||||
You may search for "Terminal" in your application launcher to find it.
|
||||
</p>
|
||||
<p>
|
||||
Alternatively use terminal shortcut for your distro if it has one by default:
|
||||
<ul>
|
||||
<li>
|
||||
<code>Ctrl-Alt-T</code>:
|
||||
Ubuntu, CentOS, Linux Mint, Elementary OS, ubermix, Kali…
|
||||
</li>
|
||||
<li>
|
||||
<code>Super-T</code>: Pop!_OS…
|
||||
</li>
|
||||
<li>
|
||||
<code>Alt-T</code>: Parrot OS…
|
||||
</li>
|
||||
<li>
|
||||
<code>Ctrl-Alt-Insert</code>: Bodhi Linux…
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
</InfoTooltip>
|
||||
</InstructionStep>
|
||||
<InstructionStep>
|
||||
<p>
|
||||
Navigate to the folder where you downloaded the file e.g.:
|
||||
</p>
|
||||
<p>
|
||||
<CopyableCommand>
|
||||
cd ~/Downloads
|
||||
</CopyableCommand>
|
||||
<InfoTooltip>
|
||||
<p>
|
||||
Press on <code>enter/return</code> key after running the command.
|
||||
</p>
|
||||
<p>
|
||||
If the file is not downloaded on Downloads folder,
|
||||
change <code>Downloads</code> to path where the file is downloaded.
|
||||
</p>
|
||||
<p>
|
||||
This command means:
|
||||
<ul>
|
||||
<li><code>cd</code> will change the current folder.</li>
|
||||
<li><code>~</code> is the user home directory.</li>
|
||||
</ul>
|
||||
</p>
|
||||
</InfoTooltip>
|
||||
</p>
|
||||
</InstructionStep>
|
||||
<InstructionStep>
|
||||
<p>
|
||||
Give the file execute permissions:
|
||||
</p>
|
||||
<p>
|
||||
<CopyableCommand>
|
||||
chmod +x {{ filename }}
|
||||
</CopyableCommand>
|
||||
<InfoTooltip>
|
||||
<p>
|
||||
Press on <code>enter/return</code> key after running the command.
|
||||
</p>
|
||||
<p>
|
||||
It will make the file executable.
|
||||
</p>
|
||||
<p>
|
||||
If you use desktop environment you can alternatively (instead of running the command):
|
||||
<ol>
|
||||
<li>Locate the file using your file manager.</li>
|
||||
<li>Right click on the file, select "Properties".</li>
|
||||
<li>Go to "Permissions" and check "Allow executing file as program".</li>
|
||||
</ol>
|
||||
</p>
|
||||
<p>
|
||||
These GUI steps and name of options may change depending on your file manager.'
|
||||
</p>
|
||||
</InfoTooltip>
|
||||
</p>
|
||||
</InstructionStep>
|
||||
<InstructionStep>
|
||||
<p>
|
||||
Execute the file:
|
||||
</p>
|
||||
<p>
|
||||
<CopyableCommand>
|
||||
./{{ filename }}
|
||||
</CopyableCommand>
|
||||
<InfoTooltip>
|
||||
If you have desktop environment, instead of running this command you can alternatively:
|
||||
<ol>
|
||||
<li>Locate the file using your file manager.</li>
|
||||
<li>Right click on the file, select "Run as program".</li>
|
||||
</ol>
|
||||
</InfoTooltip>
|
||||
</p>
|
||||
</InstructionStep>
|
||||
<InstructionStep>
|
||||
If asked, enter your administrator password.
|
||||
<InfoTooltip>
|
||||
<p>
|
||||
As you type, your password will be hidden but the keys are still
|
||||
registered, so keep typing.
|
||||
</p>
|
||||
<p>
|
||||
Press on <code>enter/return</code> key after typing your password.
|
||||
</p>
|
||||
<p>
|
||||
Administrator privileges are required to configure OS.
|
||||
</p>
|
||||
</InfoTooltip>
|
||||
</InstructionStep>
|
||||
</InstructionSteps>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import InstructionSteps from '../InstructionSteps.vue';
|
||||
import InstructionStep from '../InstructionStep.vue';
|
||||
import InfoTooltip from '../../InfoTooltip.vue';
|
||||
import CopyableCommand from '../CopyableCommand.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
CopyableCommand,
|
||||
InfoTooltip,
|
||||
InstructionSteps,
|
||||
InstructionStep,
|
||||
},
|
||||
props: {
|
||||
filename: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<InstructionSteps>
|
||||
<InstructionStep>
|
||||
Download the file.
|
||||
<InfoTooltip>
|
||||
<p>
|
||||
You should have already been prompted to save the script file.
|
||||
</p>
|
||||
<p>
|
||||
If this was not the case or you did not save the script when prompted,
|
||||
please try to download your script file again.
|
||||
</p>
|
||||
</InfoTooltip>
|
||||
</InstructionStep>
|
||||
<InstructionStep>
|
||||
Open terminal.
|
||||
<InfoTooltip>
|
||||
Type Terminal into Spotlight or open it from the Applications -> Utilities folder.
|
||||
</InfoTooltip>
|
||||
</InstructionStep>
|
||||
<InstructionStep>
|
||||
<p>
|
||||
Navigate to the folder where you downloaded the file e.g.:
|
||||
</p>
|
||||
<p>
|
||||
<CopyableCommand>
|
||||
cd ~/Downloads
|
||||
</CopyableCommand>
|
||||
<InfoTooltip>
|
||||
<p>
|
||||
Press on <code>enter/return</code> key after running the command.
|
||||
</p>
|
||||
<p>
|
||||
If the file is not downloaded on Downloads folder,
|
||||
change <code>Downloads</code> to path where the file is downloaded.
|
||||
</p>
|
||||
<p>
|
||||
This command means:
|
||||
<ul>
|
||||
<li><code>cd</code> will change the current folder.</li>
|
||||
<li><code>~</code> is the user home directory.</li>
|
||||
</ul>
|
||||
</p>
|
||||
</InfoTooltip>
|
||||
</p>
|
||||
</InstructionStep>
|
||||
<InstructionStep>
|
||||
<p>
|
||||
Give the file execute permissions:
|
||||
</p>
|
||||
<p>
|
||||
<CopyableCommand>
|
||||
chmod +x {{ filename }}
|
||||
</CopyableCommand>
|
||||
<InfoTooltip>
|
||||
<p>
|
||||
Press on <code>enter/return</code> key after running the command.
|
||||
</p>
|
||||
<p>
|
||||
It will make the file executable.
|
||||
</p>
|
||||
</InfoTooltip>
|
||||
</p>
|
||||
</InstructionStep>
|
||||
<InstructionStep>
|
||||
<p>
|
||||
Execute the file:
|
||||
</p>
|
||||
<p>
|
||||
<CopyableCommand>
|
||||
./{{ filename }}
|
||||
</CopyableCommand>
|
||||
<InfoTooltip>
|
||||
Alternatively you can locate the file in <strong>Finder</strong> and double click on it.
|
||||
</InfoTooltip>
|
||||
</p>
|
||||
</InstructionStep>
|
||||
<InstructionStep>
|
||||
If asked, enter your administrator password.
|
||||
<InfoTooltip>
|
||||
<p>
|
||||
As you type, your password will be hidden but the keys are
|
||||
still registered, so keep typing.
|
||||
</p>
|
||||
<p>
|
||||
Press on <code>enter/return</code> key after typing your password.
|
||||
</p>
|
||||
<p>
|
||||
Administrator privileges are required to configure OS.
|
||||
</p>
|
||||
</InfoTooltip>
|
||||
</InstructionStep>
|
||||
</InstructionSteps>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import InstructionSteps from '../InstructionSteps.vue';
|
||||
import InstructionStep from '../InstructionStep.vue';
|
||||
import InfoTooltip from '../../InfoTooltip.vue';
|
||||
import CopyableCommand from '../CopyableCommand.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
CopyableCommand,
|
||||
InfoTooltip,
|
||||
InstructionSteps,
|
||||
InstructionStep,
|
||||
},
|
||||
props: {
|
||||
filename: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<InstructionSteps>
|
||||
<InstructionStep>
|
||||
Download the file.
|
||||
<InfoTooltip>
|
||||
<p>If a save prompt doesn't appear, try downloading the script again.</p>
|
||||
</InfoTooltip>
|
||||
</InstructionStep>
|
||||
<InstructionStep>
|
||||
If warned by your browser, keep the file.
|
||||
<InfoTooltip>
|
||||
<!--
|
||||
Tests (15/01/2023):
|
||||
- Edge (Defender activated): "filename isn't commonly downloaded..."
|
||||
- Chrome: No warning, downloads directly.
|
||||
- Firefox: No warning, downloads directly.
|
||||
-->
|
||||
<p>
|
||||
Browsers may warn you when downloading scripts.
|
||||
</p>
|
||||
<p>
|
||||
privacy.sexy scripts are verified to be safe and are dedicated to securing your privacy.
|
||||
</p>
|
||||
<p>
|
||||
In <strong>Edge</strong>:
|
||||
<ol>
|
||||
<li>Select <strong>Keep</strong> from the downloads section.</li>
|
||||
<li>Click <strong>Show more</strong> on the next warning.</li>
|
||||
<li>Select <strong>Keep anyway</strong>.</li>
|
||||
</ol>
|
||||
</p>
|
||||
<p>
|
||||
For <strong>Firefox</strong> and <strong>Chrome</strong>, typically no additional
|
||||
action is needed.
|
||||
</p>
|
||||
</InfoTooltip>
|
||||
</InstructionStep>
|
||||
<InstructionStep>
|
||||
If your antivirus (e.g., Defender) alerts you, address the warning.
|
||||
<InfoTooltip>
|
||||
<!--
|
||||
Tests (15/01/2023):
|
||||
- Edge (Defender activated): "Couldn't download - Virus detected"
|
||||
- Chrome: "Virus detected"
|
||||
- Firefox: Does not trigger false-positive on Defender somehow.
|
||||
-->
|
||||
<p>
|
||||
Depending on the script, antivirus software may incorrectly flag the download as a threat.
|
||||
</p>
|
||||
<p>
|
||||
These false positives are common for scripts that modify system settings.
|
||||
</p>
|
||||
<p>
|
||||
To handle false warnings in Microsoft Defender:
|
||||
<ol>
|
||||
<li>Open <strong>Virus & threat protection</strong> from the Start menu.</li>
|
||||
<li>
|
||||
Locate the event in <strong>Protection history</strong>
|
||||
that pertains to the script.
|
||||
</li>
|
||||
<li>In the event details, select <strong>Actions</strong> > <strong>Allow</strong>.</li>
|
||||
<li>If the script was deleted, please re-download it.</li>
|
||||
</ol>
|
||||
</p>
|
||||
<blockquote>
|
||||
<strong>Caution:</strong> For your security, remember to:
|
||||
<ul>
|
||||
<li>Only allow scripts from trusted sources.</li>
|
||||
<li>Avoid broad exclusions in your antivirus settings.</li>
|
||||
<li>Keep real-time protection enabled whenever possible.</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
</InfoTooltip>
|
||||
</InstructionStep>
|
||||
<InstructionStep>
|
||||
<!--
|
||||
Tests (15/01/2023):
|
||||
- Edge (Defender activated): No prompts
|
||||
- Chrome: No prompts
|
||||
- Firefox: "filename is executable file. Executable files may contain..?" OK/Cancel
|
||||
-->
|
||||
Open the downloaded file.
|
||||
<InfoTooltip>
|
||||
<p>
|
||||
Confirm any browser prompts to open the file.
|
||||
</p>
|
||||
<p>
|
||||
This standard security measure ensures that you are aware of the script execution.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Firefox</strong> users, click <strong>OK</strong> to acknowledge the
|
||||
executable file warning.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Edge</strong> and <strong>Chrome</strong> users usually will not
|
||||
encounter additional prompts.
|
||||
</p>
|
||||
</InfoTooltip>
|
||||
</InstructionStep>
|
||||
<InstructionStep>
|
||||
If prompted, confirm SmartScreen warnings.
|
||||
<InfoTooltip>
|
||||
<p>
|
||||
Windows SmartScreen might display a cautionary message.
|
||||
</p>
|
||||
<p>
|
||||
This happens since privacy.sexy scripts are not recognized
|
||||
by Microsoft's certification process.
|
||||
</p>
|
||||
<p>
|
||||
<ol>
|
||||
<li>Select <strong>More info</strong>.</li>
|
||||
<li>Select <strong>Run anyway</strong>.</li>
|
||||
</ol>
|
||||
</p>
|
||||
</InfoTooltip>
|
||||
</InstructionStep>
|
||||
<InstructionStep>
|
||||
If administrative permissions are requested, grant them.
|
||||
<InfoTooltip>
|
||||
<p>
|
||||
The script may request administrative rights to apply changes.
|
||||
</p>
|
||||
<p>
|
||||
This is necessary for the script to apply the intended privacy settings.
|
||||
</p>
|
||||
<p>
|
||||
Click <strong>Yes</strong> to authorize and run the script.
|
||||
</p>
|
||||
</InfoTooltip>
|
||||
</InstructionStep>
|
||||
</InstructionSteps>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import InstructionSteps from '../InstructionSteps.vue';
|
||||
import InstructionStep from '../InstructionStep.vue';
|
||||
import InfoTooltip from '../../InfoTooltip.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
InfoTooltip,
|
||||
InstructionSteps,
|
||||
InstructionStep,
|
||||
},
|
||||
props: {
|
||||
filename: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -1,86 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { IInstructionsBuilderData, InstructionsBuilder, InstructionStepBuilderType } from '@/presentation/components/Code/CodeButtons/Save/Instructions/Data/InstructionsBuilder';
|
||||
import { IInstructionInfo, IInstructionListStep } from '@/presentation/components/Code/CodeButtons/Save/Instructions/InstructionListData';
|
||||
|
||||
describe('InstructionsBuilder', () => {
|
||||
describe('withStep', () => {
|
||||
it('returns itself', () => {
|
||||
// arrange
|
||||
const expected = new InstructionsBuilder(OperatingSystem.Android);
|
||||
const step = () => createMockStep();
|
||||
// act
|
||||
const actual = expected.withStep(step);
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('build', () => {
|
||||
it('builds with given data', () => {
|
||||
// arrange
|
||||
const expectedData = createMockData();
|
||||
const actualData = Array<IInstructionsBuilderData>();
|
||||
const builder = new InstructionsBuilder(OperatingSystem.Android);
|
||||
const steps: readonly InstructionStepBuilderType[] = [createMockStep(), createMockStep()]
|
||||
.map((step) => (data) => {
|
||||
actualData.push(data);
|
||||
return step;
|
||||
});
|
||||
for (const step of steps) {
|
||||
builder.withStep(step);
|
||||
}
|
||||
// act
|
||||
builder.build(expectedData);
|
||||
// assert
|
||||
expect(actualData.every((data) => data === expectedData));
|
||||
});
|
||||
it('builds with every step', () => {
|
||||
// arrange
|
||||
const expectedSteps = [
|
||||
createMockStep('first'),
|
||||
createMockStep('second'),
|
||||
createMockStep('third'),
|
||||
];
|
||||
const builder = new InstructionsBuilder(OperatingSystem.Android);
|
||||
const steps: readonly InstructionStepBuilderType[] = expectedSteps.map((step) => () => step);
|
||||
for (const step of steps) {
|
||||
builder.withStep(step);
|
||||
}
|
||||
// act
|
||||
const data = builder.build(createMockData());
|
||||
// assert
|
||||
const actualSteps = data.steps;
|
||||
expect(actualSteps).to.have.members(expectedSteps);
|
||||
});
|
||||
it('builds with expected OS', () => {
|
||||
// arrange
|
||||
const expected = OperatingSystem.Linux;
|
||||
const sut = new InstructionsBuilder(expected);
|
||||
// act
|
||||
const actual = sut.build(createMockData()).operatingSystem;
|
||||
// assert
|
||||
expect(true);
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createMockData(): IInstructionsBuilderData {
|
||||
return {
|
||||
fileName: 'instructions-file',
|
||||
};
|
||||
}
|
||||
|
||||
function createMockStep(identifier = 'mock step'): IInstructionListStep {
|
||||
return {
|
||||
action: createMockInfo(`${identifier} | action`),
|
||||
code: createMockInfo(`${identifier} | code`),
|
||||
};
|
||||
}
|
||||
|
||||
function createMockInfo(identifier = 'mock info'): IInstructionInfo {
|
||||
return {
|
||||
instruction: `${identifier} | mock instruction`,
|
||||
details: `${identifier} | mock details`,
|
||||
};
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { describe } from 'vitest';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { MacOsInstructionsBuilder } from '@/presentation/components/Code/CodeButtons/Save/Instructions/Data/MacOsInstructionsBuilder';
|
||||
import { runOsSpecificInstructionBuilderTests } from './OsSpecificInstructionBuilderTestRunner';
|
||||
|
||||
describe('MacOsInstructionsBuilder', () => {
|
||||
runOsSpecificInstructionBuilderTests({
|
||||
factory: () => new MacOsInstructionsBuilder(),
|
||||
os: OperatingSystem.macOS,
|
||||
});
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
import { it, expect } from 'vitest';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { InstructionsBuilder } from '@/presentation/components/Code/CodeButtons/Save/Instructions/Data/InstructionsBuilder';
|
||||
|
||||
interface ITestData {
|
||||
readonly factory: () => InstructionsBuilder;
|
||||
readonly os: OperatingSystem;
|
||||
}
|
||||
|
||||
export function runOsSpecificInstructionBuilderTests(data: ITestData) {
|
||||
it('builds multiple steps', () => {
|
||||
// arrange
|
||||
const sut = data.factory();
|
||||
// act
|
||||
const result = sut.build({ fileName: 'test.file' });
|
||||
// assert
|
||||
expect(result.steps).to.have.length.greaterThan(0);
|
||||
});
|
||||
it(`operatingSystem return ${OperatingSystem[data.os]}`, () => {
|
||||
// arrange
|
||||
const expected = data.os;
|
||||
const sut = data.factory();
|
||||
// act
|
||||
const result = sut.build({ fileName: 'test.file' });
|
||||
// assert
|
||||
expect(result.operatingSystem).to.equal(expected);
|
||||
});
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { getInstructions } from '@/presentation/components/Code/CodeButtons/Save/Instructions/InstructionListDataFactory';
|
||||
import { getEnumValues } from '@/application/Common/Enum';
|
||||
import { InstructionsBuilder } from '@/presentation/components/Code/CodeButtons/Save/Instructions/Data/InstructionsBuilder';
|
||||
import { AllSupportedOperatingSystems } from '@tests/shared/TestCases/SupportedOperatingSystems';
|
||||
|
||||
describe('InstructionListDataFactory', () => {
|
||||
describe('getInstructions', () => {
|
||||
it('returns expected if os is supported', () => {
|
||||
// arrange
|
||||
const fileName = 'test.file';
|
||||
// act
|
||||
const actualResults = AllSupportedOperatingSystems.map((os) => getInstructions(os, fileName));
|
||||
// assert
|
||||
expect(actualResults.every((result) => result instanceof InstructionsBuilder));
|
||||
});
|
||||
it('return undefined if OS is not supported', () => {
|
||||
// arrange
|
||||
const expected = undefined;
|
||||
const fileName = 'test.file';
|
||||
const unsupportedOses = getEnumValues(OperatingSystem)
|
||||
.filter((value) => !AllSupportedOperatingSystems.includes(value));
|
||||
// act
|
||||
const actualResults = unsupportedOses.map((os) => getInstructions(os, fileName));
|
||||
// assert
|
||||
expect(actualResults.every((result) => result === expected));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import CodeInstruction from '@/presentation/components/Code/CodeButtons/Save/Instructions/CodeInstruction.vue';
|
||||
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';
|
||||
@@ -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