From 756c736e21d713b8d2651cf2a9d7cf0678badde0 Mon Sep 17 00:00:00 2001 From: undergroundwires Date: Mon, 15 Jan 2024 22:38:39 +0100 Subject: [PATCH] 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. --- README.md | 2 +- .../Code/CodeButtons/Save/CodeSaveButton.vue | 16 +- .../Instructions/Data/InstructionsBuilder.ts | 28 ---- .../Data/LinuxInstructionsBuilder.ts | 92 ----------- .../Data/MacOsInstructionsBuilder.ts | 70 -------- .../Save/Instructions/InfoTooltip.vue | 21 --- .../Save/Instructions/InstructionList.vue | 119 -------------- .../Save/Instructions/InstructionListData.ts | 16 -- .../InstructionListDataFactory.ts | 19 --- .../Save/RunInstructions/InfoTooltip.vue | 31 ++++ .../Save/RunInstructions/RunInstructions.vue | 85 ++++++++++ .../Steps/CopyableCommand.vue} | 8 +- .../RunInstructions/Steps/InstructionStep.vue | 19 +++ .../Steps/InstructionSteps.vue | 13 ++ .../Steps/PlatformInstructionSteps.vue | 50 ++++++ .../Steps/Platforms/LinuxInstructions.vue | 155 ++++++++++++++++++ .../Steps/Platforms/MacOsInstructions.vue | 117 +++++++++++++ .../Steps/Platforms/WindowsInstructions.vue | 154 +++++++++++++++++ .../Data/InstructionsBuilder.spec.ts | 86 ---------- .../Data/MacOsInstructionsBuilder.spec.ts | 11 -- .../OsSpecificInstructionBuilderTestRunner.ts | 28 ---- .../InstructionListDataFactory.spec.ts | 30 ---- .../Steps/CopyableCommand.spec.ts} | 2 +- .../Steps/PlatformInstructionSteps.spec.ts | 70 ++++++++ 24 files changed, 705 insertions(+), 537 deletions(-) delete mode 100644 src/presentation/components/Code/CodeButtons/Save/Instructions/Data/InstructionsBuilder.ts delete mode 100644 src/presentation/components/Code/CodeButtons/Save/Instructions/Data/LinuxInstructionsBuilder.ts delete mode 100644 src/presentation/components/Code/CodeButtons/Save/Instructions/Data/MacOsInstructionsBuilder.ts delete mode 100644 src/presentation/components/Code/CodeButtons/Save/Instructions/InfoTooltip.vue delete mode 100644 src/presentation/components/Code/CodeButtons/Save/Instructions/InstructionList.vue delete mode 100644 src/presentation/components/Code/CodeButtons/Save/Instructions/InstructionListData.ts delete mode 100644 src/presentation/components/Code/CodeButtons/Save/Instructions/InstructionListDataFactory.ts create mode 100644 src/presentation/components/Code/CodeButtons/Save/RunInstructions/InfoTooltip.vue create mode 100644 src/presentation/components/Code/CodeButtons/Save/RunInstructions/RunInstructions.vue rename src/presentation/components/Code/CodeButtons/Save/{Instructions/CodeInstruction.vue => RunInstructions/Steps/CopyableCommand.vue} (90%) create mode 100644 src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/InstructionStep.vue create mode 100644 src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/InstructionSteps.vue create mode 100644 src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/PlatformInstructionSteps.vue create mode 100644 src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/LinuxInstructions.vue create mode 100644 src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/MacOsInstructions.vue create mode 100644 src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/WindowsInstructions.vue delete mode 100644 tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/Data/InstructionsBuilder.spec.ts delete mode 100644 tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/Data/MacOsInstructionsBuilder.spec.ts delete mode 100644 tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/Data/OsSpecificInstructionBuilderTestRunner.ts delete mode 100644 tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/InstructionListDataFactory.spec.ts rename tests/unit/presentation/components/Code/CodeButtons/Save/{Instructions/CodeInstruction.spec.ts => RunInstructions/Steps/CopyableCommand.spec.ts} (98%) create mode 100644 tests/unit/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/PlatformInstructionSteps.spec.ts diff --git a/README.md b/README.md index 01c0fd73..1ee7289a 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/src/presentation/components/Code/CodeButtons/Save/CodeSaveButton.vue b/src/presentation/components/Code/CodeButtons/Save/CodeSaveButton.vue index 4850c37a..7a73cd5d 100644 --- a/src/presentation/components/Code/CodeButtons/Save/CodeSaveButton.vue +++ b/src/presentation/components/Code/CodeButtons/Save/CodeSaveButton.vue @@ -5,8 +5,8 @@ :icon-name="isRunningAsDesktopApplication ? 'floppy-disk' : 'file-arrow-down'" @click="saveCode" /> - - + + @@ -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(() => buildFilename(currentState.value.collection.scripting)); - const instructions = computed(() => 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, }; }, diff --git a/src/presentation/components/Code/CodeButtons/Save/Instructions/Data/InstructionsBuilder.ts b/src/presentation/components/Code/CodeButtons/Save/Instructions/Data/InstructionsBuilder.ts deleted file mode 100644 index 0284a443..00000000 --- a/src/presentation/components/Code/CodeButtons/Save/Instructions/Data/InstructionsBuilder.ts +++ /dev/null @@ -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(); - - 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)), - }; - } -} diff --git a/src/presentation/components/Code/CodeButtons/Save/Instructions/Data/LinuxInstructionsBuilder.ts b/src/presentation/components/Code/CodeButtons/Save/Instructions/Data/LinuxInstructionsBuilder.ts deleted file mode 100644 index ce280c05..00000000 --- a/src/presentation/components/Code/CodeButtons/Save/Instructions/Data/LinuxInstructionsBuilder.ts +++ /dev/null @@ -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.' - + '
If this was not the case or you did not save the script when prompted,' - + '
please try to download your script file again.', - }, - })) - .withStep(() => ({ - action: { - instruction: 'Open terminal.', - details: - 'Opening terminal changes based on the distro you run.' - + '
You may search for "Terminal" in your application launcher to find it.' - + '
' - + '
Alternatively use terminal shortcut for your distro if it has one by default:' - + '
    ' - + '
  • Ctrl-Alt-T: Ubuntu, CentOS, Linux Mint, Elementary OS, ubermix, Kali…
  • ' - + '
  • Super-T: Pop!_OS…
  • ' - + '
  • Alt-T: Parrot OS…
  • ' - + '
  • Ctrl-Alt-Insert: Bodhi Linux…
  • ' - + '
' - , - }, - })) - .withStep(() => ({ - action: { - instruction: 'Navigate to the folder where you downloaded the file e.g.:', - }, - code: { - instruction: 'cd ~/Downloads', - details: 'Press on enter/return key after running the command.' - + '
If the file is not downloaded on Downloads folder,' - + '
change Downloads to path where the file is downloaded.' - + '
' - + '
This command means:' - + '
    ' - + '
  • cd will change the current folder.
  • ' - + '
  • ~ is the user home directory.
  • ' - + '
', - }, - })) - .withStep((data) => ({ - action: { - instruction: 'Give the file execute permissions:', - }, - code: { - instruction: `chmod +x ${data.fileName}`, - details: 'Press on enter/return key after running the command.
' - + 'It will make the file executable.
' - + 'If you use desktop environment you can alternatively (instead of running the command):' - + '
    ' - + '
  1. Locate the file using your file manager.
  2. ' - + '
  3. Right click on the file, select "Properties".
  4. ' - + '
  5. Go to "Permissions" and check "Allow executing file as program".
  6. ' - + '
' - + '
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:' - + '
    ' - + '
  1. Locate the file using your file manager.
  2. ' - + '
  3. Right click on the file, select "Run as program".
  4. ' - + '
' - , - }, - })) - .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.' - + '
Press on enter/return key after typing your password.' - + '
Administrator privileges are required to configure OS.', - }, - })); - } -} diff --git a/src/presentation/components/Code/CodeButtons/Save/Instructions/Data/MacOsInstructionsBuilder.ts b/src/presentation/components/Code/CodeButtons/Save/Instructions/Data/MacOsInstructionsBuilder.ts deleted file mode 100644 index b3a60860..00000000 --- a/src/presentation/components/Code/CodeButtons/Save/Instructions/Data/MacOsInstructionsBuilder.ts +++ /dev/null @@ -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.' - + '
If this was not the case or you did not save the script when prompted,' - + '
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 enter/return key after running the command.' - + '
If the file is not downloaded on Downloads folder,' - + '
change Downloads to path where the file is downloaded.' - + '
' - + '
This command means:' - + '
    ' - + '
  • cd will change the current folder.
  • ' - + '
  • ~ is the user home directory.
  • ' - + '
', - }, - })) - .withStep((data) => ({ - action: { - instruction: 'Give the file execute permissions:', - }, - code: { - instruction: `chmod +x ${data.fileName}`, - details: 'Press on enter/return key after running the command.
' - + '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 Finder 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.' - + '
Press on enter/return key after typing your password.' - + '
Administrator privileges are required to configure OS.' - , - }, - })); - } -} diff --git a/src/presentation/components/Code/CodeButtons/Save/Instructions/InfoTooltip.vue b/src/presentation/components/Code/CodeButtons/Save/Instructions/InfoTooltip.vue deleted file mode 100644 index 710157bb..00000000 --- a/src/presentation/components/Code/CodeButtons/Save/Instructions/InfoTooltip.vue +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/src/presentation/components/Code/CodeButtons/Save/Instructions/InstructionList.vue b/src/presentation/components/Code/CodeButtons/Save/Instructions/InstructionList.vue deleted file mode 100644 index c0078211..00000000 --- a/src/presentation/components/Code/CodeButtons/Save/Instructions/InstructionList.vue +++ /dev/null @@ -1,119 +0,0 @@ - - - - - diff --git a/src/presentation/components/Code/CodeButtons/Save/Instructions/InstructionListData.ts b/src/presentation/components/Code/CodeButtons/Save/Instructions/InstructionListData.ts deleted file mode 100644 index ee314548..00000000 --- a/src/presentation/components/Code/CodeButtons/Save/Instructions/InstructionListData.ts +++ /dev/null @@ -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; -} diff --git a/src/presentation/components/Code/CodeButtons/Save/Instructions/InstructionListDataFactory.ts b/src/presentation/components/Code/CodeButtons/Save/Instructions/InstructionListDataFactory.ts deleted file mode 100644 index 6d2deccb..00000000 --- a/src/presentation/components/Code/CodeButtons/Save/Instructions/InstructionListDataFactory.ts +++ /dev/null @@ -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.macOS, new MacOsInstructionsBuilder()], - [OperatingSystem.Linux, new LinuxInstructionsBuilder()], -]); - -export function getInstructions( - os: OperatingSystem, - fileName: string, -): IInstructionListData | undefined { - return builders - .get(os) - ?.build({ fileName }); -} diff --git a/src/presentation/components/Code/CodeButtons/Save/RunInstructions/InfoTooltip.vue b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/InfoTooltip.vue new file mode 100644 index 00000000..4c35a5fb --- /dev/null +++ b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/InfoTooltip.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/src/presentation/components/Code/CodeButtons/Save/RunInstructions/RunInstructions.vue b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/RunInstructions.vue new file mode 100644 index 00000000..670f2525 --- /dev/null +++ b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/RunInstructions.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/src/presentation/components/Code/CodeButtons/Save/Instructions/CodeInstruction.vue b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/CopyableCommand.vue similarity index 90% rename from src/presentation/components/Code/CodeButtons/Save/Instructions/CodeInstruction.vue rename to src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/CopyableCommand.vue index cb505a6c..9377b35b 100644 --- a/src/presentation/components/Code/CodeButtons/Save/Instructions/CodeInstruction.vue +++ b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/CopyableCommand.vue @@ -27,10 +27,10 @@ export default defineComponent({ setup() { const { copyText } = injectKey((keys) => keys.useClipboard); - const codeElement = shallowRef(); + const codeElementRef = shallowRef(); 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; diff --git a/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/InstructionStep.vue b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/InstructionStep.vue new file mode 100644 index 00000000..b1ec0c59 --- /dev/null +++ b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/InstructionStep.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/InstructionSteps.vue b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/InstructionSteps.vue new file mode 100644 index 00000000..d44dbe33 --- /dev/null +++ b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/InstructionSteps.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/PlatformInstructionSteps.vue b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/PlatformInstructionSteps.vue new file mode 100644 index 00000000..5e99fc3a --- /dev/null +++ b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/PlatformInstructionSteps.vue @@ -0,0 +1,50 @@ + + + diff --git a/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/LinuxInstructions.vue b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/LinuxInstructions.vue new file mode 100644 index 00000000..a8f88eea --- /dev/null +++ b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/LinuxInstructions.vue @@ -0,0 +1,155 @@ + + + diff --git a/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/MacOsInstructions.vue b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/MacOsInstructions.vue new file mode 100644 index 00000000..9e4fcebb --- /dev/null +++ b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/MacOsInstructions.vue @@ -0,0 +1,117 @@ + + + diff --git a/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/WindowsInstructions.vue b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/WindowsInstructions.vue new file mode 100644 index 00000000..9297cbb7 --- /dev/null +++ b/src/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/WindowsInstructions.vue @@ -0,0 +1,154 @@ + + + diff --git a/tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/Data/InstructionsBuilder.spec.ts b/tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/Data/InstructionsBuilder.spec.ts deleted file mode 100644 index 936682ee..00000000 --- a/tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/Data/InstructionsBuilder.spec.ts +++ /dev/null @@ -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(); - 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`, - }; -} diff --git a/tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/Data/MacOsInstructionsBuilder.spec.ts b/tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/Data/MacOsInstructionsBuilder.spec.ts deleted file mode 100644 index 0893688c..00000000 --- a/tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/Data/MacOsInstructionsBuilder.spec.ts +++ /dev/null @@ -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, - }); -}); diff --git a/tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/Data/OsSpecificInstructionBuilderTestRunner.ts b/tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/Data/OsSpecificInstructionBuilderTestRunner.ts deleted file mode 100644 index 5bb8db11..00000000 --- a/tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/Data/OsSpecificInstructionBuilderTestRunner.ts +++ /dev/null @@ -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); - }); -} diff --git a/tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/InstructionListDataFactory.spec.ts b/tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/InstructionListDataFactory.spec.ts deleted file mode 100644 index 01eb7bdb..00000000 --- a/tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/InstructionListDataFactory.spec.ts +++ /dev/null @@ -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)); - }); - }); -}); diff --git a/tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/CodeInstruction.spec.ts b/tests/unit/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/CopyableCommand.spec.ts similarity index 98% rename from tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/CodeInstruction.spec.ts rename to tests/unit/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/CopyableCommand.spec.ts index 05cfff69..47cbe16c 100644 --- a/tests/unit/presentation/components/Code/CodeButtons/Save/Instructions/CodeInstruction.spec.ts +++ b/tests/unit/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/CopyableCommand.spec.ts @@ -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'; diff --git a/tests/unit/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/PlatformInstructionSteps.spec.ts b/tests/unit/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/PlatformInstructionSteps.spec.ts new file mode 100644 index 00000000..b5ff27d9 --- /dev/null +++ b/tests/unit/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/PlatformInstructionSteps.spec.ts @@ -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 = { + [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; + 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, + }, + }); +}