From df273f7f635ab156ac51a8dfb3fec66c4979f1c4 Mon Sep 17 00:00:00 2001 From: undergroundwires Date: Sun, 7 Feb 2021 12:24:15 +0100 Subject: [PATCH] refactor state handling to make application available independent of the state --- CONTRIBUTING.md | 11 --- README.md | 16 +--- docs/application.md | 22 +++++ docs/presentation.md | 24 +++++ img/architecture/app-ddd.drawio | 2 +- img/architecture/app-ddd.png | Bin 26367 -> 29625 bytes src/application/ApplicationFactory.ts | 21 +++++ ...ovider.ts => ApplicationContextFactory.ts} | 16 ++-- src/application/IApplicationFactory.ts | 5 ++ .../CodeButtons/MacOsInstructions.vue | 13 ++- .../CodeButtons/TheCodeButtons.vue | 3 - src/presentation/Scripts/Cards/CardList.vue | 4 - .../Scripts/Cards/CardListItem.vue | 4 +- .../Scripts/ScriptsTree/ScriptsTree.vue | 3 - .../SelectableTree/Node/RevertToggle.vue | 3 - .../Scripts/Selector/TheSelector.vue | 4 - src/presentation/Scripts/TheOsChanger.vue | 15 ++-- src/presentation/Scripts/TheScripts.vue | 9 +- src/presentation/StatefulVue.ts | 8 +- src/presentation/TheCodeArea.vue | 3 - .../TheFooter/DownloadUrlListItem.vue | 19 ++-- src/presentation/TheFooter/PrivacyPolicy.vue | 16 ++-- src/presentation/TheFooter/TheFooter.vue | 21 ++--- src/presentation/TheHeader.vue | 16 ++-- src/presentation/TheSearchBar.vue | 3 - .../application/ApplicationFactory.spec.ts | 60 +++++++++++++ .../Context/ApplicationContextFactory.spec.ts | 83 ++++++++++++++++++ .../ApplicationContextProvider.spec.ts | 69 --------------- 28 files changed, 275 insertions(+), 198 deletions(-) create mode 100644 docs/application.md create mode 100644 docs/presentation.md create mode 100644 src/application/ApplicationFactory.ts rename src/application/Context/{ApplicationContextProvider.ts => ApplicationContextFactory.ts} (66%) create mode 100644 src/application/IApplicationFactory.ts create mode 100644 tests/unit/application/ApplicationFactory.spec.ts create mode 100644 tests/unit/application/Context/ApplicationContextFactory.spec.ts delete mode 100644 tests/unit/application/Context/ApplicationContextProvider.spec.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f2f25280..5322e5c4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,17 +23,6 @@ - ❗ 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) -## 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 By contributing, you agree that your contributions will be licensed under its GNU General Public License v3.0. diff --git a/README.md b/README.md index fb07ad56..73b20270 100644 --- a/README.md +++ b/README.md @@ -54,26 +54,14 @@ 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` -## Architecture +## Architecture overview ### Application - Powered by **TypeScript**, **Vue.js** and **Electron** 💪 - and driven by **Domain-driven design**, **Event-driven architecture**, **Data-driven programming** concepts. - Application uses highly decoupled models & services in different DDD layers. - - **Domain layer** is where the application is modelled with validation logic. - - **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. +- 📖 Read more on • [Presentation](./docs/presentation.md) • [Application](./docs/application.md) ![DDD + vue.js](img/architecture/app-ddd.png) diff --git a/docs/application.md b/docs/application.md new file mode 100644 index 00000000..f1093683 --- /dev/null +++ b/docs/application.md @@ -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. diff --git a/docs/presentation.md b/docs/presentation.md new file mode 100644 index 00000000..9c984b63 --- /dev/null +++ b/docs/presentation.md @@ -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. diff --git a/img/architecture/app-ddd.drawio b/img/architecture/app-ddd.drawio index 6a2880fa..4df829db 100644 --- a/img/architecture/app-ddd.drawio +++ b/img/architecture/app-ddd.drawio @@ -1 +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== \ No newline at end of file +3VtZd6JKF/01/dhZjA6PREjCXRbGFpM2L70QCYIoLsUI/PpvnwKcMD3cazrJl6wEqfHU3vvsKoz5Infm6e3KWU5ZPPGiL5IwSb/I+hdJUsUmflNBVhQoDaEo8FfBpCgS9wWDIPfKwqrZJph466OGSRxHSbA8LnTjxcJzk6MyZ7WKt8fNnuPoeNal43u1goHrRPXSx2CSTIvSlirsy++8wJ9WM4tCWTN3qsZlwXrqTOLtQZFsfJE7qzhOilfztONFhF2FS9Hv5pXaXWArb5H8Tofr+9ZNO9J+6A+Ljvr89N2aP5tflSq4JKtW7E0AQHkbr5Jp7McLJzL2pdereLOYeDSsgLt9m24cL1EoojD0kiQr2XQ2SYyiaTKPylovDZLv1P1KLe9GBzV6Wo7Mb7LqZpGssoNOdDs6rNt343dVv3WyimdeJ47iFV+f3OZfqKkDWGK6jjcr1/sJapUQnZXvJT9pJxXtCNGDCUp6br147iFSNFh5kZMEL8eSc0rl+rt2e3LxouT3T7iW3oXrHW9HrO1J/IC8yZfmjXfVVisnO2iwjINFsj4Y+Z4K0KD0TrnMzdI51ZP0/nlrSVRPBFNMv5fPbh3/QVHyu7rHgXcIV5L6m/bxeWSofCj7KMd9caJNOdP9yltj8Zg7XtSEcEzzdhok3mDpcFy2OCkcU/ocRNEBxhPVa02Uc+i3pLHcaFCPeJFUOjuzFVbBeqvES38KXlnbaB6nT5lN2/2eL1c7+/Rgv69OMxdHW268Z2qJR6n1m5klHmeW9B6pJb1Xav07h28fm7YoH5n2L9tLzebbm7zc+ixHxPcU1MWPDP/JPaSaV2vLZRS4l7DqI+OtWfezSt/nyGjwr3KEg/Li62ck/b6Lq8cuvnsYO7Rx6YyNN9/MxtXPkjwf6flK/pQHJLmWdHo8d4LLHo3G4mTyfBZ3UWjKbe8Njkbtk6Rq15NKUs4klfpWSaXUgDYXzysHiGzcZLPyLgr4c8v1XPcc4OOWqqjCGwDePDmLNs4A3vqbLtaoAe69eOXJ5hWgxT8H+tlrnAd60myPBeEyO0TrBNtmHdvmGWilt4K2WYN25S3jdZDE5eCfGl75zAb8V+Ft1a3CWCRB8hmxPfWFdwe3XQM3ma48ZxIs/P8DeN/dGsT6KX5Of165rO2OBU/2GufAFbyW0GpdBlwcTo7BFZQ6uOqVeAZe6a3grZ/X8DKY/OoZ6aNCLKniVfvDgazWQF4nTvKzI9qf42vo9H0ZFE9MQKze/XhHCP/K6euSGJ6+mXoORPHcu6lvh2H9mOUcviEiRLEfuJ8HULG+Nf1dQHdr2AOazamXG0eR5xKqH1efHw/N+lZUA289dZb0MpjzTywcQkUrh5QjLQr8BcoSeptoV9p1xl50T48UJHVZH8dJEs/RIKKKa8ed+ZyYowMXfaEJn0xbL4tPVtBBy6lunoOUqLwu49GnSUIfydAICenGnSyUq8CNF88BKF9duZhRusHe6uBC5VDHDZIuXn91FpOv4xV+U5FKh54budH88bDxwvUPaoKDUOtqWZ0pL/pk3aa/UpxyryhXap38fenl6a9vkp+ffvmP6f/qTOKxtxOB1Gr9GDjr9QXpl9TWVVP9PQlI5zUgvZkI6tv85xeB+EsRjBdb/AbFKX7wCtsyFdNbausLsS7LyhHlcuOqVT+QSO0zht9+K7Lr55HPT7bwS7Kf45XvLb6uvZg+TXDTAC83y4C/Z7v+Gi+TYB7k/ER2wZzfvUtSsS/XqK/0cUh9VfZfqH/Y3Hrj5vegr7fv2awhLwLh8cyf5n7J/CWIPS+Z8+Qe8V/RzamUteJWulm/gJzrlM58nfs7S3rKrpXxY7pxcyFw7r4Jrh6/dOWJPMlUmWXqizt3X1iobVmnnU/mbmDeTZZPd9/i+4GZWfYoMG+nkfM4iSe6ELBwKJnBteQ8Psj9eVtBm62paz4v103RCkzMfX/r+k/zaD1Gj/G8vXkamMV9R8wmj2l0P/gnmswfNmPp28wMldZIirKRlEbm7dNyfLttmwHL+uE/N0wwMLtFs6Qs9Om1unt9Z7bN0Mh6HfPlPkwXB33Vb7OH62/hLCjLEnfxsH6yi1i8+UM2zniUd9fTya3vPyFK2zawfkVkuulbubGxwijs2qbUBS7WQBC6oZtbgSBYgSJaobthoZl27SGVSyzQUqujCMweJqhPcfWZrW0se5Z17f6G5SMZ2KDtKLcGGq6mYOpMxb1gzY0MV8nKtOoKPA2fdajdKMOcVC4yiSVWpsiWbSbMZhjbxNgzxDFCHKZIYzEepylYmZAjnhx1PuJRmO6jD9aUz1LiqBsaQq8jpKjLMJZv6RjH9uWubaTd0M+tx3MxaRTTpmcPJawb8/Zlby4kWBvKZqqpu2jLtpbEUo4jxwBz2kOxWLshW4GWsUDJe3bfZ/kM8ZBmGOZkSq+j5bQ+FjJoiWL1wYO76el+jv4y4tiygaaijYR+GBu42po0sk1wxfEtr4hzoG27wIjpmkj4WzraZVgvjU9zow/TXQXrABY+8Qeeh4SJivi2TGcJHz830MYgvMCvoIBrzkcP2Fq8DeFPPDDU9WX0zxnVvcqpkHfDmQAN4DpUvYArdzv6/i02b/ttcyakPYo3NGl9W8xHvFPcWyg+QY5izQblGcadIXcFgc+n+xwP8CsWmhymLBBIu+grEB8CcEW8Ptqwck0zWgv0CDz1IepGGLt/Hs8O8BwoWyuc+Wifs9wKGdeARtio4C4j3Vvh0IfuVHALfg1gPEOsBtbaV0x9RPFluE8pZ4ABrn3oxkQe+FuKE30zWgt+gP1MfY1bU+8Tf9Ay8kDvS6RDi17nlAsjhWvtkdY7pHFIY4h1poInmluxOhrmdIUe8gXYiozj7UNrTCy4nAms0KqAssQKSQt96Jj0MiIfkICpwmwXGoWOwdPrsRJOrgz8ZCt/CLkubZYjZ0n3AmLJMJZq6W5i0Rj5CJgMEYNJmMiESY+0w/EeIUcpTkMs8sYAf8CNuAk06Arj5CbnGXy/ih/4Jx3JRR4b4hkdKtxrMi3t6S7laT7KMa+tCUUu+6RB4lXqwc8QP7h3eU739D7mIe0apPuUUUz6kDREeKo9e5T0qL3OuEYseFGXvIzy5/bVvCFNy/C3jPN2W3ohjYs1kkd0aT2hKUKLaW+gKD3ySfJpvQ//MEhjyG3kAvyD8XgE0qxI8XA/DYeUx7S2DJiCY7STXo0HOsB6uIZd1ZKXCTiBRkzu9z19BLwoj42kyEsT+qdYfLHUYIo4Ea+m0N6CeAXue1inRdjkM65dC+OAb8xtSow8PSM9ukmP+1c/ezVXqd4eCZQnaC+McoPvNSzTuIfhWuwPpF/eZkQ5grWT12jEPfj0aayUv87dpPBt4piwYrTPUR5R/iiv40T+oEHPtA/McuSpCK1k2DtoPInZ/4RYM+FAuSgSLvBZeBe45X7F84G4zfg+m1VtuYbLMu20n1/1IzzIO7G/kW/tvL+o2/Xf9ava0rqrGE777eYF5zx/4Iamfj0lbjAW4uvnZZ7Bi4wyV3gcx/WYAzGU9dcN2+7THqLynON7Vz8d5aSZ4UE8/dN4hAMcgAnXu0px4QwjHuC2679fB28LXWrJHhPe73DMPaaFXgn7Au+BdgbvPTZFjgAdySjXDB/RWbVu0jr5vQLfOq2vtFPUL+Kgmyst9/ZGcDrXM5wcLcxH5wHSqkjnLkv/Fv4Rd3WshDpW7BQr9V9j1flbWGEs7rczn2so9zOce6CxmVLstxo8XeBeWJwrNRWemvL9Jx8mHMP8aQo/ynCu86trNU9RT35J50BXIo/r2eRptNe78B/yf9pvyZ/orGls6ZxSxK3RtYxBK2OqYtCKmIr85vtOEdNTwwxadFLvtBf34fblCUroyniiyRV6hrrMnz8l+Uo9fuNJlurPoY0zz6GNP34Oxe3+v8qKTwHv/zVPNv4H \ No newline at end of file diff --git a/img/architecture/app-ddd.png b/img/architecture/app-ddd.png index 8b6390bc605ce673b4a63474c009d54a14fa1f65..2deaa2e2ecc89bf79eb8b7d20de4ecf09cbe0e7b 100644 GIT binary patch literal 29625 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 uoFl6l5qFd3QlY(-#Jx#SK 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 literal 26367 zcmb@tby$>7)HhD2lz=E5(h^H8CABo{F0r(9F0d@U%hHXcpooIBbO|CTQWAnF(jl#q z(j^V=?el%!>-zow{;q5H-gBRsIdkUBoH-MpeQW^JqM~4?z{A6%($Q8o!o$PI0$*7& z5`bgayFP%2M^@yoVeXH@x*$EB@pvU6|53aW;%;a^e_ly-UI_^d1|#a^YmX5{qJcLc z359k-x*(mM{!5k+2g!+mBt#^@2ysbXNmX$v;2{MP1%YHO|I2Uh>g@HuvY==j($mwP zS3*--R2&c`V21Q^LSy^@5{3XCAaQ^#sRmF$f(#((ABmNb5(Iu!y}do1&72+dkpBP5 zOM*ouWdOE-o;Cui&np1|XiubvGw`G3?C631R|Mkbi$(z)O|YD(xTvI@l(eX= zmHaQk8)&58|FZvaBsBnIIw9>{eeF?zY~=qM%hyd`)*Yt-mGv++4MG~r$(n!z{uAJ3 z4=DOS_BH@SiGcnSB5CREiI0mBTW*-1@k=63`whuD#fJke5;t;N29aRl5Le5kcD(#}} z0)wjSs;arGIlHQPsbW-REp)sM?48{F)QzRZ{iW=6Jj~R5p{PK0C{Q~J=INwq2tu0Z zI>I!4f*_tSIMmC*$XgDjCt z2M=<0@�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�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 O6n8ZBG|Fz;g#8y;hlVu( diff --git a/src/application/ApplicationFactory.ts b/src/application/ApplicationFactory.ts new file mode 100644 index 00000000..c737a780 --- /dev/null +++ b/src/application/ApplicationFactory.ts @@ -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; + protected constructor(costlyGetter: ApplicationGetter) { + if (!costlyGetter) { + throw new Error('undefined getter'); + } + this.getter = new AsyncLazy(() => Promise.resolve(costlyGetter())); + } + public getAppAsync(): Promise { + return this.getter.getValueAsync(); + } +} diff --git a/src/application/Context/ApplicationContextProvider.ts b/src/application/Context/ApplicationContextFactory.ts similarity index 66% rename from src/application/Context/ApplicationContextProvider.ts rename to src/application/Context/ApplicationContextFactory.ts index 1a8bb4ff..e3000ae7 100644 --- a/src/application/Context/ApplicationContextProvider.ts +++ b/src/application/Context/ApplicationContextFactory.ts @@ -4,15 +4,15 @@ import { OperatingSystem } from '@/domain/OperatingSystem'; import { Environment } from '../Environment/Environment'; import { IApplication } from '@/domain/IApplication'; import { IEnvironment } from '../Environment/IEnvironment'; -import { parseApplication } from '../Parser/ApplicationParser'; +import { IApplicationFactory } from '../IApplicationFactory'; +import { ApplicationFactory } from '../ApplicationFactory'; -export type ApplicationParserType = () => IApplication; -const ApplicationParser: ApplicationParserType = parseApplication; - -export function buildContext( - parser = ApplicationParser, - environment = Environment.CurrentEnvironment): IApplicationContext { - const app = parser(); +export async function buildContextAsync( + factory: IApplicationFactory = ApplicationFactory.Current, + environment = Environment.CurrentEnvironment): Promise { + if (!factory) { throw new Error('undefined factory'); } + if (!environment) { throw new Error('undefined environment'); } + const app = await factory.getAppAsync(); const os = getInitialOs(app, environment); return new ApplicationContext(app, os); } diff --git a/src/application/IApplicationFactory.ts b/src/application/IApplicationFactory.ts new file mode 100644 index 00000000..a0cca695 --- /dev/null +++ b/src/application/IApplicationFactory.ts @@ -0,0 +1,5 @@ +import { IApplication } from '@/domain/IApplication'; + +export interface IApplicationFactory { + getAppAsync(): Promise; +} diff --git a/src/presentation/CodeButtons/MacOsInstructions.vue b/src/presentation/CodeButtons/MacOsInstructions.vue index 07f6fae9..17fd1360 100644 --- a/src/presentation/CodeButtons/MacOsInstructions.vue +++ b/src/presentation/CodeButtons/MacOsInstructions.vue @@ -80,29 +80,26 @@ diff --git a/src/presentation/CodeButtons/TheCodeButtons.vue b/src/presentation/CodeButtons/TheCodeButtons.vue index dfce4cfd..f358c114 100644 --- a/src/presentation/CodeButtons/TheCodeButtons.vue +++ b/src/presentation/CodeButtons/TheCodeButtons.vue @@ -65,9 +65,6 @@ export default class TheCodeButtons extends StatefulVue { } } - protected initialize(): void { - return; - } protected handleCollectionState(newState: ICategoryCollectionState): void { this.isMacOsCollection = newState.collection.os === OperatingSystem.macOS; this.fileName = buildFileName(newState.collection.scripting); diff --git a/src/presentation/Scripts/Cards/CardList.vue b/src/presentation/Scripts/Cards/CardList.vue index f39c45e4..5d23d563 100644 --- a/src/presentation/Scripts/Cards/CardList.vue +++ b/src/presentation/Scripts/Cards/CardList.vue @@ -22,7 +22,6 @@ import { StatefulVue } from '@/presentation/StatefulVue'; import { ICategory } from '@/domain/ICategory'; import { hasDirective } from './NonCollapsingDirective'; import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState'; -import { IApplication } from '@/domain/IApplication'; @Component({ components: { @@ -45,9 +44,6 @@ export default class CardList extends StatefulVue { this.activeCategoryId = isExpanded ? categoryId : undefined; } - protected initialize(app: IApplication): void { - return; - } protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void { this.setCategories(newState.collection.actions); this.activeCategoryId = undefined; diff --git a/src/presentation/Scripts/Cards/CardListItem.vue b/src/presentation/Scripts/Cards/CardListItem.vue index 01b2067a..76f72179 100644 --- a/src/presentation/Scripts/Cards/CardListItem.vue +++ b/src/presentation/Scripts/Cards/CardListItem.vue @@ -78,9 +78,7 @@ export default class CardListItem extends StatefulVue { this.cardTitle = category ? category.name : undefined; await this.updateSelectionIndicatorsAsync(value); } - protected initialize(): void { - return; - } + protected handleCollectionState(): void { return; } diff --git a/src/presentation/Scripts/ScriptsTree/ScriptsTree.vue b/src/presentation/Scripts/ScriptsTree/ScriptsTree.vue index ebf47963..7d53af09 100644 --- a/src/presentation/Scripts/ScriptsTree/ScriptsTree.vue +++ b/src/presentation/Scripts/ScriptsTree/ScriptsTree.vue @@ -73,9 +73,6 @@ export default class ScriptsTree extends StatefulVue { (category: ICategory) => node.id === getCategoryNodeId(category)); } - protected initialize(): void { - return; - } protected async handleCollectionState(newState: ICategoryCollectionState) { this.setCurrentFilter(newState.filter.currentFilter); if (!this.categoryId) { diff --git a/src/presentation/Scripts/ScriptsTree/SelectableTree/Node/RevertToggle.vue b/src/presentation/Scripts/ScriptsTree/SelectableTree/Node/RevertToggle.vue index b558c338..e43d38f9 100644 --- a/src/presentation/Scripts/ScriptsTree/SelectableTree/Node/RevertToggle.vue +++ b/src/presentation/Scripts/ScriptsTree/SelectableTree/Node/RevertToggle.vue @@ -37,9 +37,6 @@ export default class RevertToggle extends StatefulVue { this.handler.selectWithRevertState(this.isReverted, context.state.selection); } - protected initialize(): void { - return; - } protected handleCollectionState(newState: ICategoryCollectionState): void { this.updateStatus(newState.selection.selectedScripts); this.events.unsubscribeAll(); diff --git a/src/presentation/Scripts/Selector/TheSelector.vue b/src/presentation/Scripts/Selector/TheSelector.vue index 5930e953..a7d46246 100644 --- a/src/presentation/Scripts/Selector/TheSelector.vue +++ b/src/presentation/Scripts/Selector/TheSelector.vue @@ -56,7 +56,6 @@ import { IScript } from '@/domain/IScript'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript'; import { RecommendationLevel } from '@/domain/RecommendationLevel'; import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState'; -import { IApplication } from '@/domain/IApplication'; enum SelectionState { Standard, @@ -82,9 +81,6 @@ export default class TheSelector extends StatefulVue { selectType(context.state, type); } - protected initialize(app: IApplication): void { - return; - } protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void { this.updateSelections(newState); newState.selection.changed.on(() => this.updateSelections(newState)); diff --git a/src/presentation/Scripts/TheOsChanger.vue b/src/presentation/Scripts/TheOsChanger.vue index 02d0998f..d034ca65 100644 --- a/src/presentation/Scripts/TheOsChanger.vue +++ b/src/presentation/Scripts/TheOsChanger.vue @@ -16,23 +16,24 @@ import { Component } from 'vue-property-decorator'; import { OperatingSystem } from '@/domain/OperatingSystem'; import { StatefulVue } from '@/presentation/StatefulVue'; import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState'; -import { IApplication } from '@/domain/IApplication'; +import { ApplicationFactory } from '@/application/ApplicationFactory'; @Component export default class TheOsChanger extends StatefulVue { 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) { const context = await this.getCurrentContextAsync(); context.changeContext(newOs); } - protected initialize(app: IApplication): void { - this.allOses = app.getSupportedOsList() - .map((os) => ({ os, name: renderOsName(os) })); - } - protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void { + protected handleCollectionState(newState: ICategoryCollectionState): void { this.currentOs = newState.os; this.$forceUpdate(); // v-bind:class is not updated otherwise } diff --git a/src/presentation/Scripts/TheScripts.vue b/src/presentation/Scripts/TheScripts.vue index 8d3475f7..8aaea94c 100644 --- a/src/presentation/Scripts/TheScripts.vue +++ b/src/presentation/Scripts/TheScripts.vue @@ -49,7 +49,7 @@ import { StatefulVue } from '@/presentation/StatefulVue'; import { Grouping } from './Grouping/Grouping'; import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult'; 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 */ @Component({ @@ -78,6 +78,10 @@ export default class TheScripts extends StatefulVue { public isSearching = false; public searchHasMatches = false; + public async created() { + const app = await ApplicationFactory.Current.getAppAsync(); + this.repositoryUrl = app.info.repositoryWebUrl; + } public async clearSearchQueryAsync() { const context = await this.getCurrentContextAsync(); const filter = context.state.filter; @@ -87,9 +91,6 @@ export default class TheScripts extends StatefulVue { this.currentGrouping = group; } - protected initialize(app: IApplication): void { - this.repositoryUrl = app.info.repositoryWebUrl; - } protected handleCollectionState(newState: ICategoryCollectionState): void { this.events.unsubscribeAll(); this.subscribeState(newState); diff --git a/src/presentation/StatefulVue.ts b/src/presentation/StatefulVue.ts index c852881a..61db812c 100644 --- a/src/presentation/StatefulVue.ts +++ b/src/presentation/StatefulVue.ts @@ -1,17 +1,15 @@ import { Component, Vue } from 'vue-property-decorator'; import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy'; 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 { IApplication } from '@/domain/IApplication'; import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState'; import { EventSubscriptionCollection } from '../infrastructure/Events/EventSubscriptionCollection'; // @ts-ignore because https://github.com/vuejs/vue-class-component/issues/91 @Component export abstract class StatefulVue extends Vue { - public static instance = new AsyncLazy( - () => Promise.resolve(buildContext())); + private static readonly instance = new AsyncLazy(() => buildContextAsync()); protected readonly events = new EventSubscriptionCollection(); @@ -20,7 +18,6 @@ export abstract class StatefulVue extends Vue { public async mounted() { const context = await this.getCurrentContextAsync(); this.ownEvents.register(context.contextChanged.on((event) => this.handleStateChangedEvent(event))); - this.initialize(context.app); this.handleCollectionState(context.state, undefined); } public destroyed() { @@ -28,7 +25,6 @@ export abstract class StatefulVue extends Vue { this.events.unsubscribeAll(); } - protected abstract initialize(app: IApplication): void; protected abstract handleCollectionState( newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined): void; protected getCurrentContextAsync(): Promise { diff --git a/src/presentation/TheCodeArea.vue b/src/presentation/TheCodeArea.vue index d1b598fb..9495f401 100644 --- a/src/presentation/TheCodeArea.vue +++ b/src/presentation/TheCodeArea.vue @@ -26,9 +26,6 @@ export default class TheCodeArea extends StatefulVue { this.destroyEditor(); } - protected initialize(): void { - return; - } protected handleCollectionState(newState: ICategoryCollectionState): void { this.destroyEditor(); this.editor = initializeEditor(this.theme, this.editorId, newState.collection.scripting.language); diff --git a/src/presentation/TheFooter/DownloadUrlListItem.vue b/src/presentation/TheFooter/DownloadUrlListItem.vue index 646fe04e..fdc59f7f 100644 --- a/src/presentation/TheFooter/DownloadUrlListItem.vue +++ b/src/presentation/TheFooter/DownloadUrlListItem.vue @@ -9,15 +9,13 @@ diff --git a/src/presentation/TheFooter/TheFooter.vue b/src/presentation/TheFooter/TheFooter.vue index b540e38e..4f7cdfd2 100644 --- a/src/presentation/TheFooter/TheFooter.vue +++ b/src/presentation/TheFooter/TheFooter.vue @@ -47,22 +47,21 @@ diff --git a/src/presentation/TheHeader.vue b/src/presentation/TheHeader.vue index 3dc0a1f1..d8ad5ffb 100644 --- a/src/presentation/TheHeader.vue +++ b/src/presentation/TheHeader.vue @@ -1,27 +1,23 @@ diff --git a/src/presentation/TheSearchBar.vue b/src/presentation/TheSearchBar.vue index e6457517..efc5971e 100644 --- a/src/presentation/TheSearchBar.vue +++ b/src/presentation/TheSearchBar.vue @@ -36,9 +36,6 @@ export default class TheSearchBar extends StatefulVue { } } - protected initialize(): void { - return; - } protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined) { const totalScripts = newState.collection.totalScripts; this.searchPlaceHolder = `Search in ${totalScripts} scripts`; diff --git a/tests/unit/application/ApplicationFactory.spec.ts b/tests/unit/application/ApplicationFactory.spec.ts new file mode 100644 index 00000000..71ed4215 --- /dev/null +++ b/tests/unit/application/ApplicationFactory.spec.ts @@ -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); + } +} diff --git a/tests/unit/application/Context/ApplicationContextFactory.spec.ts b/tests/unit/application/Context/ApplicationContextFactory.spec.ts new file mode 100644 index 00000000..0c5cd329 --- /dev/null +++ b/tests/unit/application/Context/ApplicationContextFactory.spec.ts @@ -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), + }; +} diff --git a/tests/unit/application/Context/ApplicationContextProvider.spec.ts b/tests/unit/application/Context/ApplicationContextProvider.spec.ts deleted file mode 100644 index 1b9fddfa..00000000 --- a/tests/unit/application/Context/ApplicationContextProvider.spec.ts +++ /dev/null @@ -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); -}