refactor state handling to make application available independent of the state
This commit is contained in:
@@ -23,17 +23,6 @@
|
|||||||
- ❗ DON'T
|
- ❗ DON'T
|
||||||
- Do not update the versions, current version is only [set by the maintainer](./img/architecture/gitops.png) and updated automatically by [bump-everywhere](https://github.com/undergroundwires/bump-everywhere)
|
- Do not update the versions, current version is only [set by the maintainer](./img/architecture/gitops.png) and updated automatically by [bump-everywhere](https://github.com/undergroundwires/bump-everywhere)
|
||||||
|
|
||||||
## Guidelines
|
|
||||||
|
|
||||||
### Handle the state in presentation layer
|
|
||||||
|
|
||||||
- There are two types of components:
|
|
||||||
- **Stateless**, extends `Vue`
|
|
||||||
- **Stateful**, extends [`StatefulVue`](./src/presentation/StatefulVue.ts)
|
|
||||||
- The source of truth for the state lies in application layer ([`./src/application/`](src/application/)) and must be updated from the views if they're mutating the state
|
|
||||||
- They mutate or/and react to state changes in [ApplicationContext](src/application/Context/ApplicationContext.ts).
|
|
||||||
- You can react by getting the state and listening to it and update the view accordingly in [`mounted()`](https://vuejs.org/v2/api/#mounted) method.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
By contributing, you agree that your contributions will be licensed under its GNU General Public License v3.0.
|
By contributing, you agree that your contributions will be licensed under its GNU General Public License v3.0.
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -54,26 +54,14 @@
|
|||||||
1. Build: `docker build -t undergroundwires/privacy.sexy:0.9.1 .`
|
1. Build: `docker build -t undergroundwires/privacy.sexy:0.9.1 .`
|
||||||
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.9.1 undergroundwires/privacy.sexy:0.9.1`
|
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.9.1 undergroundwires/privacy.sexy:0.9.1`
|
||||||
|
|
||||||
## Architecture
|
## Architecture overview
|
||||||
|
|
||||||
### Application
|
### Application
|
||||||
|
|
||||||
- Powered by **TypeScript**, **Vue.js** and **Electron** 💪
|
- Powered by **TypeScript**, **Vue.js** and **Electron** 💪
|
||||||
- and driven by **Domain-driven design**, **Event-driven architecture**, **Data-driven programming** concepts.
|
- and driven by **Domain-driven design**, **Event-driven architecture**, **Data-driven programming** concepts.
|
||||||
- Application uses highly decoupled models & services in different DDD layers.
|
- Application uses highly decoupled models & services in different DDD layers.
|
||||||
- **Domain layer** is where the application is modelled with validation logic.
|
- 📖 Read more on • [Presentation](./docs/presentation.md) • [Application](./docs/application.md)
|
||||||
- **Presentation Layer**
|
|
||||||
- Consists of Vue.js components and other UI-related code.
|
|
||||||
- Desktop application is created using [Electron](https://www.electronjs.org/).
|
|
||||||
- Event driven as in components simply listens to events from the state and act accordingly.
|
|
||||||
- **Application Layer**
|
|
||||||
- Keeps the application state using [state pattern](https://en.wikipedia.org/wiki/State_pattern)
|
|
||||||
- [ApplicationContext](src/application/Context/ApplicationContext.ts)
|
|
||||||
- Holds the [CategoryCollectionState](src/application/Context/State/CategoryCollectionState.ts)] for each OS
|
|
||||||
- Same instance is shared throughout the application
|
|
||||||
- The scripts are defined and controlled in [yaml files](src/application/collections/) per OS
|
|
||||||
- Uses [data-driven programming](https://en.wikipedia.org/wiki/Data-driven_programming)
|
|
||||||
- 📖 See [extend scripts](#extend-scripts) to read about how to extend them.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
22
docs/application.md
Normal file
22
docs/application.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Application
|
||||||
|
|
||||||
|
- It's mainly responsible for
|
||||||
|
- creating and event based [application state](#application-state)
|
||||||
|
- parsing and compiling [application data](#application-data)
|
||||||
|
|
||||||
|
## Application state
|
||||||
|
|
||||||
|
- [ApplicationContext.ts](./../src/application/Context/ApplicationContext.ts) holds the [CategoryCollectionState](./../src/application/Context/State/CategoryCollectionState.ts)] for each OS
|
||||||
|
- Uses [state pattern](https://en.wikipedia.org/wiki/State_pattern)
|
||||||
|
- Same instance is shared throughout the application to ensure consistent state
|
||||||
|
- 📖 See [Application State | Presentation layer](./presentation.md#application-state) to read more about how the state should be managed by the presentation layer.
|
||||||
|
- 📖 See [ApplicationContext.ts](./../src/application/Context/ApplicationContext.ts) to start diving into the state code.
|
||||||
|
|
||||||
|
## Application data
|
||||||
|
|
||||||
|
- Compiled to `Application` domain object.
|
||||||
|
- The scripts are defined and controlled in different data files per OS
|
||||||
|
- Enables [data-driven programming](https://en.wikipedia.org/wiki/Data-driven_programming) and easier contributions
|
||||||
|
- Application data is defined in collection fil es and
|
||||||
|
- 📖 See [Application data | Presentation layer](./presentation.md#application-data) to read how the application data is read by the presentation layer.
|
||||||
|
- 📖 See [collection files documentation](./collection-files.md) to read more about how the data files are structured/defined and see [collection yaml files](./../src/application/collections/) to directly check the code.
|
||||||
24
docs/presentation.md
Normal file
24
docs/presentation.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Presentation layer
|
||||||
|
|
||||||
|
- Consists of Vue.js components and other UI-related code.
|
||||||
|
- Desktop application is created using [Electron](https://www.electronjs.org/).
|
||||||
|
- Event driven as in components simply listens to events from the state and act accordingly.
|
||||||
|
|
||||||
|
## Application data
|
||||||
|
|
||||||
|
- Components and should use [ApplicationFactory](./../src/application/ApplicationFactory.ts) singleton to reach the application domain.
|
||||||
|
- [Application.ts](../src/domain/Application.ts) domain model is the stateless application representation including
|
||||||
|
- available scripts, collections as defined in [collection files](./collection-files.md)
|
||||||
|
- package information as defined in [`package.json`](./../package.json)
|
||||||
|
- 📖 See [Application data | Application layer](./presentation.md#application-data) where application data is parsed and compiled.
|
||||||
|
|
||||||
|
## Application state
|
||||||
|
|
||||||
|
- Stateful components mutate or/and react to state changes in [ApplicationContext](./../src/application/Context/ApplicationContext.ts).
|
||||||
|
- Stateless components that does not handle state extends `Vue`
|
||||||
|
- Stateful components that depends on the collection state such as user selection, search queries and more extends [`StatefulVue`](./../src/presentation/StatefulVue.ts)
|
||||||
|
- The single source of truth is a singleton of the state created and made available to presentation layer by [`StatefulVue`](./../src/presentation/StatefulVue.ts)
|
||||||
|
- `StatefulVue` includes abstract `handleCollectionState` that is fired once the component is loaded and also each time [collection](./collection-files.md) is changed.
|
||||||
|
- Do not forget to subscribe from events when component is destroyed or if needed [collection](./collection-files.md) is changed.
|
||||||
|
- 💡 `events` in base class [`StatefulVue`](./../src/presentation/StatefulVue.ts) makes lifecycling easier
|
||||||
|
- 📖 See [Application state | Application layer](./presentation.md#application-state) where the state is implemented using using state pattern.
|
||||||
@@ -1 +1 @@
|
|||||||
<mxfile host="www.draw.io" modified="2019-12-27T03:04:27.829Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" etag="O-1eaon4mqUmgvki0auB" version="12.4.3" pages="1"><diagram id="rhL8jzEM8kVVyiS98U7u" name="Page-1">3Zpdk6I4FIZ/jZdakPDlpa3tzlTNbnVVV83OzE1XhACZAUKF2Or8+k0wCBhau1f8GrpayUkC5HlzTg7IAE7T9V8M5fHfNMDJABjBegBnAwA8YIpPadhsDS50t4aIkWBrMmvDM/mNldFQ1iUJcNFqyClNOMnbRp9mGfZ5y4YYo6t2s5Am7bPmKMKa4dlHiW79lwQ8VsOyjdr+CZMors5sGqomRVVjZShiFNBVwwQfB3DKKOXbvXQ9xYlkV3HZ9pu/Ubu7MIYz/p4OD0/efJxMXmZfs6kd/vj2Txp+HlrVxfFNNWIcCACqSBmPaUQzlDzW1gdGl1mA5WENUarbfKE0F0ZTGH9izjdKTbTkVJhiniaqFq8J/ya7j2xV+t6oma3VkcvCpipknG0anWTxe7Ou7laWqn4FZ/QXntKEsnJ8cFxuomY7cjncN4kqU0GXzMcHMFYzE7EI8wPtwE534S+YplhcqejHcII4eW1fB1IzN9q1q8UVO0rfj2gNrqL1TreWarWI96AbPFG3suuEMbRpNMgpyXjROPKTNIgGKnZC5Zsqctp77n24NTDtvQmzPX09fXbjOGFGwatGj0bsMEbAfmf4uONpaF0zfKjjvqJkqc70xHAhxirOTTNtIrRlXsWE4+cclRhWIlNoSxqSJGkwDmzsBVYXfQ8soOPIHjTj1TyrrgwzjteHxdDhqQ6O23Yf5U2res2H1coeN9Z7xzgTbehc07XMlmu907PMtmeBm3AtcCHX+n8RftwO2iZsBe2j7YHrnj/IQ+9eUsSbmlCnpgwnRQ+gxepJnifE7yNUtwKvFrpDW/51ieGUmzpCw77ddiKdFMXtdhTf3Yw1wzjoCOPu2cK4fS/Oc9P3V/AeEiSoOd2Mpoj0mxotzCAIO7mbhgvH+Ayp0XjPqca6UwGrw6nsczmVpYH+nIUMCSJLny8Z7hV46PnY97uALzzbso0zAHf3clGnA7h3ySjmaMDxK1aZzRugzY+DDrHTDTpwxwvD6GeF8PbYujpbtwMtOBdaV0PLcE4Lwqk6+F3jhR0L8EXxenqoeMw44ffIdj8uXB3uWIPLY4ZRQLLoD8B79dBg6ll8Kn9e6TfsLpDvBbALLoCWZQf9wBXJSRuuYelw7ZHZgRecC6+er4ldEhy7R7pVxMA2R+Obg2xrkAuO+KEU7R18e6C15+xm9ZTjiqjOk2X1MbOc47DMrqej52Olp02o+YDDSGhE/NsDZ+pLymXBVWPoBjfaiNFfG9rtMdMXCg1SEaNc7pK0fJ+giUTCEHyTSUKiTNi4fIizs35BC5w8yYRfTlw4W1DOaSoaJLLiAfm/olKAVjokN9GkPNmkyLfvPcg0CFWFkKylZA/qemYx5/KFiYkkAeZ+kFkj4tMsJEJaNvLFGcFcrHxIfEm7iDlz4UK0GKIsGC6Y+JQmW6Ykc+i4L1+X+GfxIpuINMUb5VXG1+t9r60Jb1/yMQPQ16771x1+WPchCugC79QHnvfyjIqiR91NcGPC6yvx/QtvHhV+ka3Ep5B1Lf7FnlgYpFk+3Sp6Uhq4+zkqBCNdbQh1tStb/2rrucT9q20cVTukLMLZsMBU/rI/d8Stwjwn5fPTYkhzTlLyu0wKenR0WN0O7MTXpbc6pLc+LL0o1m/WbX8JrV9PhI//AQ==</diagram></mxfile>
|
<mxfile host="Electron" modified="2021-01-31T12:32:01.751Z" agent="5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.1.8 Chrome/87.0.4280.88 Electron/11.1.1 Safari/537.36" etag="OTbSPW1ZOLwiPL6mt-j9" version="14.1.8" type="device"><diagram id="rhL8jzEM8kVVyiS98U7u" name="Page-1">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</diagram></mxfile>
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 29 KiB |
21
src/application/ApplicationFactory.ts
Normal file
21
src/application/ApplicationFactory.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { IApplication } from '@/domain/IApplication';
|
||||||
|
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
|
||||||
|
import { IApplicationFactory } from './IApplicationFactory';
|
||||||
|
import { parseApplication } from './Parser/ApplicationParser';
|
||||||
|
|
||||||
|
export type ApplicationGetter = () => IApplication;
|
||||||
|
const ApplicationGetter: ApplicationGetter = parseApplication;
|
||||||
|
|
||||||
|
export class ApplicationFactory implements IApplicationFactory {
|
||||||
|
public static readonly Current: IApplicationFactory = new ApplicationFactory(ApplicationGetter);
|
||||||
|
private readonly getter: AsyncLazy<IApplication>;
|
||||||
|
protected constructor(costlyGetter: ApplicationGetter) {
|
||||||
|
if (!costlyGetter) {
|
||||||
|
throw new Error('undefined getter');
|
||||||
|
}
|
||||||
|
this.getter = new AsyncLazy<IApplication>(() => Promise.resolve(costlyGetter()));
|
||||||
|
}
|
||||||
|
public getAppAsync(): Promise<IApplication> {
|
||||||
|
return this.getter.getValueAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,15 +4,15 @@ import { OperatingSystem } from '@/domain/OperatingSystem';
|
|||||||
import { Environment } from '../Environment/Environment';
|
import { Environment } from '../Environment/Environment';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
import { IApplication } from '@/domain/IApplication';
|
||||||
import { IEnvironment } from '../Environment/IEnvironment';
|
import { IEnvironment } from '../Environment/IEnvironment';
|
||||||
import { parseApplication } from '../Parser/ApplicationParser';
|
import { IApplicationFactory } from '../IApplicationFactory';
|
||||||
|
import { ApplicationFactory } from '../ApplicationFactory';
|
||||||
|
|
||||||
export type ApplicationParserType = () => IApplication;
|
export async function buildContextAsync(
|
||||||
const ApplicationParser: ApplicationParserType = parseApplication;
|
factory: IApplicationFactory = ApplicationFactory.Current,
|
||||||
|
environment = Environment.CurrentEnvironment): Promise<IApplicationContext> {
|
||||||
export function buildContext(
|
if (!factory) { throw new Error('undefined factory'); }
|
||||||
parser = ApplicationParser,
|
if (!environment) { throw new Error('undefined environment'); }
|
||||||
environment = Environment.CurrentEnvironment): IApplicationContext {
|
const app = await factory.getAppAsync();
|
||||||
const app = parser();
|
|
||||||
const os = getInitialOs(app, environment);
|
const os = getInitialOs(app, environment);
|
||||||
return new ApplicationContext(app, os);
|
return new ApplicationContext(app, os);
|
||||||
}
|
}
|
||||||
5
src/application/IApplicationFactory.ts
Normal file
5
src/application/IApplicationFactory.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { IApplication } from '@/domain/IApplication';
|
||||||
|
|
||||||
|
export interface IApplicationFactory {
|
||||||
|
getAppAsync(): Promise<IApplication>;
|
||||||
|
}
|
||||||
@@ -80,29 +80,26 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop } from 'vue-property-decorator';
|
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
|
||||||
import Code from './Code.vue';
|
import Code from './Code.vue';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import { ApplicationFactory } from '@/application/ApplicationFactory';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
Code,
|
Code,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class MacOsInstructions extends StatefulVue {
|
export default class MacOsInstructions extends Vue {
|
||||||
@Prop() public fileName: string;
|
@Prop() public fileName: string;
|
||||||
public appName = '';
|
public appName = '';
|
||||||
public macOsDownloadUrl = '';
|
public macOsDownloadUrl = '';
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
public async created() {
|
||||||
|
const app = await ApplicationFactory.Current.getAppAsync();
|
||||||
this.appName = app.info.name;
|
this.appName = app.info.name;
|
||||||
this.macOsDownloadUrl = app.info.getDownloadUrl(OperatingSystem.macOS);
|
this.macOsDownloadUrl = app.info.getDownloadUrl(OperatingSystem.macOS);
|
||||||
}
|
}
|
||||||
protected handleCollectionState(): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -65,9 +65,6 @@ export default class TheCodeButtons extends StatefulVue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState): void {
|
protected handleCollectionState(newState: ICategoryCollectionState): void {
|
||||||
this.isMacOsCollection = newState.collection.os === OperatingSystem.macOS;
|
this.isMacOsCollection = newState.collection.os === OperatingSystem.macOS;
|
||||||
this.fileName = buildFileName(newState.collection.scripting);
|
this.fileName = buildFileName(newState.collection.scripting);
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import { StatefulVue } from '@/presentation/StatefulVue';
|
|||||||
import { ICategory } from '@/domain/ICategory';
|
import { ICategory } from '@/domain/ICategory';
|
||||||
import { hasDirective } from './NonCollapsingDirective';
|
import { hasDirective } from './NonCollapsingDirective';
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
@@ -45,9 +44,6 @@ export default class CardList extends StatefulVue {
|
|||||||
this.activeCategoryId = isExpanded ? categoryId : undefined;
|
this.activeCategoryId = isExpanded ? categoryId : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
||||||
this.setCategories(newState.collection.actions);
|
this.setCategories(newState.collection.actions);
|
||||||
this.activeCategoryId = undefined;
|
this.activeCategoryId = undefined;
|
||||||
|
|||||||
@@ -78,9 +78,7 @@ export default class CardListItem extends StatefulVue {
|
|||||||
this.cardTitle = category ? category.name : undefined;
|
this.cardTitle = category ? category.name : undefined;
|
||||||
await this.updateSelectionIndicatorsAsync(value);
|
await this.updateSelectionIndicatorsAsync(value);
|
||||||
}
|
}
|
||||||
protected initialize(): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protected handleCollectionState(): void {
|
protected handleCollectionState(): void {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,9 +73,6 @@ export default class ScriptsTree extends StatefulVue {
|
|||||||
(category: ICategory) => node.id === getCategoryNodeId(category));
|
(category: ICategory) => node.id === getCategoryNodeId(category));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protected async handleCollectionState(newState: ICategoryCollectionState) {
|
protected async handleCollectionState(newState: ICategoryCollectionState) {
|
||||||
this.setCurrentFilter(newState.filter.currentFilter);
|
this.setCurrentFilter(newState.filter.currentFilter);
|
||||||
if (!this.categoryId) {
|
if (!this.categoryId) {
|
||||||
|
|||||||
@@ -37,9 +37,6 @@ export default class RevertToggle extends StatefulVue {
|
|||||||
this.handler.selectWithRevertState(this.isReverted, context.state.selection);
|
this.handler.selectWithRevertState(this.isReverted, context.state.selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState): void {
|
protected handleCollectionState(newState: ICategoryCollectionState): void {
|
||||||
this.updateStatus(newState.selection.selectedScripts);
|
this.updateStatus(newState.selection.selectedScripts);
|
||||||
this.events.unsubscribeAll();
|
this.events.unsubscribeAll();
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ import { IScript } from '@/domain/IScript';
|
|||||||
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
||||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
|
||||||
|
|
||||||
enum SelectionState {
|
enum SelectionState {
|
||||||
Standard,
|
Standard,
|
||||||
@@ -82,9 +81,6 @@ export default class TheSelector extends StatefulVue {
|
|||||||
selectType(context.state, type);
|
selectType(context.state, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
||||||
this.updateSelections(newState);
|
this.updateSelections(newState);
|
||||||
newState.selection.changed.on(() => this.updateSelections(newState));
|
newState.selection.changed.on(() => this.updateSelections(newState));
|
||||||
|
|||||||
@@ -16,23 +16,24 @@ import { Component } from 'vue-property-decorator';
|
|||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
import { StatefulVue } from '@/presentation/StatefulVue';
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
import { ApplicationFactory } from '@/application/ApplicationFactory';
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class TheOsChanger extends StatefulVue {
|
export default class TheOsChanger extends StatefulVue {
|
||||||
public allOses: Array<{ name: string, os: OperatingSystem }> = [];
|
public allOses: Array<{ name: string, os: OperatingSystem }> = [];
|
||||||
public currentOs: OperatingSystem = undefined;
|
public currentOs: OperatingSystem = OperatingSystem.Unknown;
|
||||||
|
|
||||||
|
public async created() {
|
||||||
|
const app = await ApplicationFactory.Current.getAppAsync();
|
||||||
|
this.allOses = app.getSupportedOsList()
|
||||||
|
.map((os) => ({ os, name: renderOsName(os) }));
|
||||||
|
}
|
||||||
public async changeOsAsync(newOs: OperatingSystem) {
|
public async changeOsAsync(newOs: OperatingSystem) {
|
||||||
const context = await this.getCurrentContextAsync();
|
const context = await this.getCurrentContextAsync();
|
||||||
context.changeContext(newOs);
|
context.changeContext(newOs);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
protected handleCollectionState(newState: ICategoryCollectionState): void {
|
||||||
this.allOses = app.getSupportedOsList()
|
|
||||||
.map((os) => ({ os, name: renderOsName(os) }));
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
|
||||||
this.currentOs = newState.os;
|
this.currentOs = newState.os;
|
||||||
this.$forceUpdate(); // v-bind:class is not updated otherwise
|
this.$forceUpdate(); // v-bind:class is not updated otherwise
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ import { StatefulVue } from '@/presentation/StatefulVue';
|
|||||||
import { Grouping } from './Grouping/Grouping';
|
import { Grouping } from './Grouping/Grouping';
|
||||||
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
import { ApplicationFactory } from '@/application/ApplicationFactory';
|
||||||
|
|
||||||
/** Shows content of single category or many categories */
|
/** Shows content of single category or many categories */
|
||||||
@Component({
|
@Component({
|
||||||
@@ -78,6 +78,10 @@ export default class TheScripts extends StatefulVue {
|
|||||||
public isSearching = false;
|
public isSearching = false;
|
||||||
public searchHasMatches = false;
|
public searchHasMatches = false;
|
||||||
|
|
||||||
|
public async created() {
|
||||||
|
const app = await ApplicationFactory.Current.getAppAsync();
|
||||||
|
this.repositoryUrl = app.info.repositoryWebUrl;
|
||||||
|
}
|
||||||
public async clearSearchQueryAsync() {
|
public async clearSearchQueryAsync() {
|
||||||
const context = await this.getCurrentContextAsync();
|
const context = await this.getCurrentContextAsync();
|
||||||
const filter = context.state.filter;
|
const filter = context.state.filter;
|
||||||
@@ -87,9 +91,6 @@ export default class TheScripts extends StatefulVue {
|
|||||||
this.currentGrouping = group;
|
this.currentGrouping = group;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
|
||||||
this.repositoryUrl = app.info.repositoryWebUrl;
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState): void {
|
protected handleCollectionState(newState: ICategoryCollectionState): void {
|
||||||
this.events.unsubscribeAll();
|
this.events.unsubscribeAll();
|
||||||
this.subscribeState(newState);
|
this.subscribeState(newState);
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import { Component, Vue } from 'vue-property-decorator';
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
|
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
|
||||||
import { IApplicationContext } from '@/application/Context/IApplicationContext';
|
import { IApplicationContext } from '@/application/Context/IApplicationContext';
|
||||||
import { buildContext } from '@/application/Context/ApplicationContextProvider';
|
import { buildContextAsync } from '@/application/Context/ApplicationContextFactory';
|
||||||
import { IApplicationContextChangedEvent } from '@/application/Context/IApplicationContext';
|
import { IApplicationContextChangedEvent } from '@/application/Context/IApplicationContext';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||||
import { EventSubscriptionCollection } from '../infrastructure/Events/EventSubscriptionCollection';
|
import { EventSubscriptionCollection } from '../infrastructure/Events/EventSubscriptionCollection';
|
||||||
|
|
||||||
// @ts-ignore because https://github.com/vuejs/vue-class-component/issues/91
|
// @ts-ignore because https://github.com/vuejs/vue-class-component/issues/91
|
||||||
@Component
|
@Component
|
||||||
export abstract class StatefulVue extends Vue {
|
export abstract class StatefulVue extends Vue {
|
||||||
public static instance = new AsyncLazy<IApplicationContext>(
|
private static readonly instance = new AsyncLazy<IApplicationContext>(() => buildContextAsync());
|
||||||
() => Promise.resolve(buildContext()));
|
|
||||||
|
|
||||||
protected readonly events = new EventSubscriptionCollection();
|
protected readonly events = new EventSubscriptionCollection();
|
||||||
|
|
||||||
@@ -20,7 +18,6 @@ export abstract class StatefulVue extends Vue {
|
|||||||
public async mounted() {
|
public async mounted() {
|
||||||
const context = await this.getCurrentContextAsync();
|
const context = await this.getCurrentContextAsync();
|
||||||
this.ownEvents.register(context.contextChanged.on((event) => this.handleStateChangedEvent(event)));
|
this.ownEvents.register(context.contextChanged.on((event) => this.handleStateChangedEvent(event)));
|
||||||
this.initialize(context.app);
|
|
||||||
this.handleCollectionState(context.state, undefined);
|
this.handleCollectionState(context.state, undefined);
|
||||||
}
|
}
|
||||||
public destroyed() {
|
public destroyed() {
|
||||||
@@ -28,7 +25,6 @@ export abstract class StatefulVue extends Vue {
|
|||||||
this.events.unsubscribeAll();
|
this.events.unsubscribeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract initialize(app: IApplication): void;
|
|
||||||
protected abstract handleCollectionState(
|
protected abstract handleCollectionState(
|
||||||
newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined): void;
|
newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined): void;
|
||||||
protected getCurrentContextAsync(): Promise<IApplicationContext> {
|
protected getCurrentContextAsync(): Promise<IApplicationContext> {
|
||||||
|
|||||||
@@ -26,9 +26,6 @@ export default class TheCodeArea extends StatefulVue {
|
|||||||
this.destroyEditor();
|
this.destroyEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState): void {
|
protected handleCollectionState(newState: ICategoryCollectionState): void {
|
||||||
this.destroyEditor();
|
this.destroyEditor();
|
||||||
this.editor = initializeEditor(this.theme, this.editorId, newState.collection.scripting.language);
|
this.editor = initializeEditor(this.theme, this.editorId, newState.collection.scripting.language);
|
||||||
|
|||||||
@@ -9,15 +9,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop, Watch } from 'vue-property-decorator';
|
import { Component, Prop, Watch, Vue } from 'vue-property-decorator';
|
||||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
|
||||||
import { Environment } from '@/application/Environment/Environment';
|
import { Environment } from '@/application/Environment/Environment';
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ApplicationFactory } from '@/application/ApplicationFactory';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class DownloadUrlListItem extends StatefulVue {
|
export default class DownloadUrlListItem extends Vue {
|
||||||
@Prop() public operatingSystem!: OperatingSystem;
|
@Prop() public operatingSystem!: OperatingSystem;
|
||||||
|
|
||||||
public downloadUrl: string = '';
|
public downloadUrl: string = '';
|
||||||
@@ -38,16 +36,9 @@ export default class DownloadUrlListItem extends StatefulVue {
|
|||||||
this.hasCurrentOsDesktopVersion = hasDesktopVersion(currentOs);
|
this.hasCurrentOsDesktopVersion = hasDesktopVersion(currentOs);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getDownloadUrlAsync(os: OperatingSystem): Promise<string> {
|
private async getDownloadUrlAsync(os: OperatingSystem): Promise<string> {
|
||||||
const context = await this.getCurrentContextAsync();
|
const context = await ApplicationFactory.Current.getAppAsync();
|
||||||
return context.app.info.getDownloadUrl(os);
|
return context.info.getDownloadUrl(os);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,25 +31,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component } from 'vue-property-decorator';
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
|
||||||
import { Environment } from '@/application/Environment/Environment';
|
import { Environment } from '@/application/Environment/Environment';
|
||||||
|
import { ApplicationFactory } from '@/application/ApplicationFactory';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
import { IApplication } from '@/domain/IApplication';
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class PrivacyPolicy extends StatefulVue {
|
export default class PrivacyPolicy extends Vue {
|
||||||
public repositoryUrl: string = '';
|
public repositoryUrl: string = '';
|
||||||
public feedbackUrl: string = '';
|
public feedbackUrl: string = '';
|
||||||
public isDesktop = Environment.CurrentEnvironment.isDesktop;
|
public isDesktop = Environment.CurrentEnvironment.isDesktop;
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
public async created() {
|
||||||
|
const app = await ApplicationFactory.Current.getAppAsync();
|
||||||
|
this.initialize(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initialize(app: IApplication) {
|
||||||
const info = app.info;
|
const info = app.info;
|
||||||
this.repositoryUrl = info.repositoryWebUrl;
|
this.repositoryUrl = info.repositoryWebUrl;
|
||||||
this.feedbackUrl = info.feedbackUrl;
|
this.feedbackUrl = info.feedbackUrl;
|
||||||
}
|
}
|
||||||
protected handleCollectionState(): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -47,22 +47,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component } from 'vue-property-decorator';
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
|
||||||
import { Environment } from '@/application/Environment/Environment';
|
import { Environment } from '@/application/Environment/Environment';
|
||||||
import PrivacyPolicy from './PrivacyPolicy.vue';
|
import PrivacyPolicy from './PrivacyPolicy.vue';
|
||||||
import DownloadUrlList from './DownloadUrlList.vue';
|
import DownloadUrlList from './DownloadUrlList.vue';
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
|
||||||
import { IApplication } from '@/domain/IApplication';
|
import { IApplication } from '@/domain/IApplication';
|
||||||
|
import { ApplicationFactory } from '@/application/ApplicationFactory';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
PrivacyPolicy, DownloadUrlList,
|
PrivacyPolicy, DownloadUrlList,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class TheFooter extends StatefulVue {
|
export default class TheFooter extends Vue {
|
||||||
public readonly modalName = 'privacy-policy';
|
public readonly modalName = 'privacy-policy';
|
||||||
public readonly isDesktop: boolean;
|
public readonly isDesktop = Environment.CurrentEnvironment.isDesktop;
|
||||||
|
|
||||||
public version: string = '';
|
public version: string = '';
|
||||||
public repositoryUrl: string = '';
|
public repositoryUrl: string = '';
|
||||||
@@ -70,12 +69,12 @@ export default class TheFooter extends StatefulVue {
|
|||||||
public feedbackUrl: string = '';
|
public feedbackUrl: string = '';
|
||||||
public homepageUrl: string = '';
|
public homepageUrl: string = '';
|
||||||
|
|
||||||
constructor() {
|
public async created() {
|
||||||
super();
|
const app = await ApplicationFactory.Current.getAppAsync();
|
||||||
this.isDesktop = Environment.CurrentEnvironment.isDesktop;
|
this.initialize(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
private initialize(app: IApplication) {
|
||||||
const info = app.info;
|
const info = app.info;
|
||||||
this.version = info.version;
|
this.version = info.version;
|
||||||
this.homepageUrl = info.homepage;
|
this.homepageUrl = info.homepage;
|
||||||
@@ -83,10 +82,6 @@ export default class TheFooter extends StatefulVue {
|
|||||||
this.releaseUrl = info.releaseUrl;
|
this.releaseUrl = info.releaseUrl;
|
||||||
this.feedbackUrl = info.feedbackUrl;
|
this.feedbackUrl = info.feedbackUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,27 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
<h1 class="child title" >{{ title }}</h1>
|
<h1 class="child title" >{{ title }}</h1>
|
||||||
<h2 class="child subtitle">Enforce privacy & security on Windows and macOS</h2>
|
<h2 class="child subtitle">Enforce privacy & security on Windows and macOS</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ApplicationFactory } from '@/application/ApplicationFactory';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
import { Component } from 'vue-property-decorator';
|
|
||||||
import { StatefulVue } from './StatefulVue';
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class TheHeader extends StatefulVue {
|
export default class TheHeader extends Vue {
|
||||||
public title = '';
|
public title = '';
|
||||||
public subtitle = '';
|
public subtitle = '';
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
public async created() {
|
||||||
|
const app = await ApplicationFactory.Current.getAppAsync();
|
||||||
this.title = app.info.name;
|
this.title = app.info.name;
|
||||||
}
|
}
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -36,9 +36,6 @@ export default class TheSearchBar extends StatefulVue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined) {
|
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined) {
|
||||||
const totalScripts = newState.collection.totalScripts;
|
const totalScripts = newState.collection.totalScripts;
|
||||||
this.searchPlaceHolder = `Search in ${totalScripts} scripts`;
|
this.searchPlaceHolder = `Search in ${totalScripts} scripts`;
|
||||||
|
|||||||
60
tests/unit/application/ApplicationFactory.spec.ts
Normal file
60
tests/unit/application/ApplicationFactory.spec.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { ApplicationFactory, ApplicationGetter } from '@/application/ApplicationFactory';
|
||||||
|
import { ApplicationStub } from '../stubs/ApplicationStub';
|
||||||
|
|
||||||
|
describe('ApplicationFactory', () => {
|
||||||
|
describe('ctor', () => {
|
||||||
|
it('throws if getter is undefined', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'undefined getter';
|
||||||
|
const getter = undefined;
|
||||||
|
// act
|
||||||
|
const act = () => new SystemUnderTest(getter);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('getAppAsync', () => {
|
||||||
|
it('returns result from the getter', async () => {
|
||||||
|
// arrange
|
||||||
|
const expected = new ApplicationStub();
|
||||||
|
const getter: ApplicationGetter = () => expected;
|
||||||
|
const sut = new SystemUnderTest(getter);
|
||||||
|
// act
|
||||||
|
const actual = await Promise.all( [
|
||||||
|
sut.getAppAsync(),
|
||||||
|
sut.getAppAsync(),
|
||||||
|
sut.getAppAsync(),
|
||||||
|
sut.getAppAsync(),
|
||||||
|
]);
|
||||||
|
// assert
|
||||||
|
expect(actual.every((value) => value === expected));
|
||||||
|
});
|
||||||
|
it('only executes getter once', async () => {
|
||||||
|
// arrange
|
||||||
|
let totalExecution = 0;
|
||||||
|
const expected = new ApplicationStub();
|
||||||
|
const getter: ApplicationGetter = () => {
|
||||||
|
totalExecution++;
|
||||||
|
return expected;
|
||||||
|
};
|
||||||
|
const sut = new SystemUnderTest(getter);
|
||||||
|
// act
|
||||||
|
await Promise.all( [
|
||||||
|
sut.getAppAsync(),
|
||||||
|
sut.getAppAsync(),
|
||||||
|
sut.getAppAsync(),
|
||||||
|
sut.getAppAsync(),
|
||||||
|
]);
|
||||||
|
// assert
|
||||||
|
expect(totalExecution).to.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
class SystemUnderTest extends ApplicationFactory {
|
||||||
|
public constructor(costlyGetter: ApplicationGetter) {
|
||||||
|
super(costlyGetter);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||||
|
import { CategoryCollectionStub } from '../../stubs/CategoryCollectionStub';
|
||||||
|
import { EnvironmentStub } from '../../stubs/EnvironmentStub';
|
||||||
|
import { ApplicationStub } from '../../stubs/ApplicationStub';
|
||||||
|
import { buildContextAsync } from '@/application/Context/ApplicationContextFactory';
|
||||||
|
import { IApplicationFactory } from '@/application/IApplicationFactory';
|
||||||
|
import { IApplication } from '@/domain/IApplication';
|
||||||
|
|
||||||
|
describe('ApplicationContextFactory', () => {
|
||||||
|
describe('buildContextAsync', () => {
|
||||||
|
describe('factory', () => {
|
||||||
|
it('sets application from factory', async () => {
|
||||||
|
// arrange
|
||||||
|
const expected = new ApplicationStub().withCollection(
|
||||||
|
new CategoryCollectionStub().withOs(OperatingSystem.macOS));
|
||||||
|
const factoryMock = mockFactoryWithApp(expected);
|
||||||
|
// act
|
||||||
|
const context = await buildContextAsync(factoryMock);
|
||||||
|
// assert
|
||||||
|
expect(expected).to.equal(context.app);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('environment', () => {
|
||||||
|
describe('sets initial OS as expected', () => {
|
||||||
|
it('returns currentOs if it is supported', async () => {
|
||||||
|
// arrange
|
||||||
|
const expected = OperatingSystem.Windows;
|
||||||
|
const environment = new EnvironmentStub().withOs(expected);
|
||||||
|
const collection = new CategoryCollectionStub().withOs(expected);
|
||||||
|
const factoryMock = mockFactoryWithCollection(collection);
|
||||||
|
// act
|
||||||
|
const context = await buildContextAsync(factoryMock, environment);
|
||||||
|
// assert
|
||||||
|
const actual = context.state.os;
|
||||||
|
expect(expected).to.equal(actual);
|
||||||
|
});
|
||||||
|
it('fallbacks to other os if OS in environment is not supported', async () => {
|
||||||
|
// arrange
|
||||||
|
const expected = OperatingSystem.Windows;
|
||||||
|
const currentOs = OperatingSystem.macOS;
|
||||||
|
const environment = new EnvironmentStub().withOs(currentOs);
|
||||||
|
const collection = new CategoryCollectionStub().withOs(expected);
|
||||||
|
const factoryMock = mockFactoryWithCollection(collection);
|
||||||
|
// act
|
||||||
|
const context = await buildContextAsync(factoryMock, environment);
|
||||||
|
// assert
|
||||||
|
const actual = context.state.os;
|
||||||
|
expect(expected).to.equal(actual);
|
||||||
|
});
|
||||||
|
it('fallbacks to most supported os if current os is not supported', async () => {
|
||||||
|
// arrange
|
||||||
|
const expectedOs = OperatingSystem.Android;
|
||||||
|
const allCollections = [
|
||||||
|
new CategoryCollectionStub().withOs(OperatingSystem.Linux).withTotalScripts(3),
|
||||||
|
new CategoryCollectionStub().withOs(expectedOs).withTotalScripts(5),
|
||||||
|
new CategoryCollectionStub().withOs(OperatingSystem.Windows).withTotalScripts(4),
|
||||||
|
];
|
||||||
|
const environment = new EnvironmentStub().withOs(OperatingSystem.macOS);
|
||||||
|
const app = new ApplicationStub().withCollections(...allCollections);
|
||||||
|
const factoryMock = mockFactoryWithApp(app);
|
||||||
|
// act
|
||||||
|
const context = await buildContextAsync(factoryMock, environment);
|
||||||
|
// assert
|
||||||
|
const actual = context.state.os;
|
||||||
|
expect(expectedOs).to.equal(actual, `Expected: ${OperatingSystem[expectedOs]}, actual: ${OperatingSystem[actual]}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function mockFactoryWithCollection(result: ICategoryCollection): IApplicationFactory {
|
||||||
|
return mockFactoryWithApp(new ApplicationStub().withCollection(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockFactoryWithApp(app: IApplication): IApplicationFactory {
|
||||||
|
return {
|
||||||
|
getAppAsync: () => Promise.resolve(app),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import 'mocha';
|
|
||||||
import { expect } from 'chai';
|
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
||||||
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
|
||||||
import { ApplicationParserType, buildContext } from '@/application/Context/ApplicationContextProvider';
|
|
||||||
import { CategoryCollectionStub } from '../../stubs/CategoryCollectionStub';
|
|
||||||
import { EnvironmentStub } from '../../stubs/EnvironmentStub';
|
|
||||||
import { ApplicationStub } from '../../stubs/ApplicationStub';
|
|
||||||
|
|
||||||
describe('ApplicationContextProvider', () => {
|
|
||||||
describe('buildContext', () => {
|
|
||||||
it('sets application from parser', () => {
|
|
||||||
// arrange
|
|
||||||
const expected = new ApplicationStub().withCollection(
|
|
||||||
new CategoryCollectionStub().withOs(OperatingSystem.macOS));
|
|
||||||
const parserMock: ApplicationParserType = () => expected;
|
|
||||||
// act
|
|
||||||
const context = buildContext(parserMock);
|
|
||||||
// assert
|
|
||||||
expect(expected).to.equal(context.app);
|
|
||||||
});
|
|
||||||
describe('sets initial OS as expected', () => {
|
|
||||||
it('returns currentOs if it is supported', () => {
|
|
||||||
// arrange
|
|
||||||
const expected = OperatingSystem.Windows;
|
|
||||||
const environment = new EnvironmentStub().withOs(expected);
|
|
||||||
const parser = mockParser(new CategoryCollectionStub().withOs(expected));
|
|
||||||
// act
|
|
||||||
const context = buildContext(parser, environment);
|
|
||||||
// assert
|
|
||||||
const actual = context.state.os;
|
|
||||||
expect(expected).to.equal(actual);
|
|
||||||
});
|
|
||||||
it('fallbacks to other os if OS in environment is not supported', () => {
|
|
||||||
// arrange
|
|
||||||
const expected = OperatingSystem.Windows;
|
|
||||||
const currentOs = OperatingSystem.macOS;
|
|
||||||
const environment = new EnvironmentStub().withOs(currentOs);
|
|
||||||
const parser = mockParser(new CategoryCollectionStub().withOs(expected));
|
|
||||||
// act
|
|
||||||
const context = buildContext(parser, environment);
|
|
||||||
// assert
|
|
||||||
const actual = context.state.os;
|
|
||||||
expect(expected).to.equal(actual);
|
|
||||||
});
|
|
||||||
it('fallbacks to most supported os if current os is not supported', () => {
|
|
||||||
// arrange
|
|
||||||
const expectedOs = OperatingSystem.Android;
|
|
||||||
const allCollections = [
|
|
||||||
new CategoryCollectionStub().withOs(OperatingSystem.Linux).withTotalScripts(3),
|
|
||||||
new CategoryCollectionStub().withOs(expectedOs).withTotalScripts(5),
|
|
||||||
new CategoryCollectionStub().withOs(OperatingSystem.Windows).withTotalScripts(4),
|
|
||||||
];
|
|
||||||
const environment = new EnvironmentStub().withOs(OperatingSystem.macOS);
|
|
||||||
const app = new ApplicationStub().withCollections(...allCollections);
|
|
||||||
const parser: ApplicationParserType = () => app;
|
|
||||||
// act
|
|
||||||
const context = buildContext(parser, environment);
|
|
||||||
// assert
|
|
||||||
const actual = context.state.os;
|
|
||||||
expect(expectedOs).to.equal(actual, `Expected: ${OperatingSystem[expectedOs]}, actual: ${OperatingSystem[actual]}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function mockParser(result: ICategoryCollection): ApplicationParserType {
|
|
||||||
return () => new ApplicationStub().withCollection(result);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user