Make starting of application modular and unit testable (#5324)

* Introduce injection token as competition for injectable setup to have better control of timing of different setups

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using competition to setup app paths

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using competition for setupping IPC channel listeners

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Stop running setups in unit tests without need

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to running injection token based setups over legacy DI setups in unit tests

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate naming for running setups

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to running injection token based setups over legacy DI setups in application roots

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Adapt to typing changes in injectable

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Update injectable

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce concept of runnable as a way to delegate runs to Open Closed Principle compliant runnables

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Deprecate vars in favor of injectables

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate loading of extensions to injectable instead of setup-code

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Flag injectable causing side effects

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Adapt injectable to auto register using a plugin instead of internal feature that no longer exists

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Simplify late registrations

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce tokens for runnables of specific application events for Open Closed Principle

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate setup events to more granular timeslots

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Reimplement setup for catalog syncing using runnables instead of non-OCP in index.ts

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Reimplement setup for application menu using runnables instead of non-OCP in index.ts

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Reimplement setup for electron application name using runnables instead of non-OCP in index.ts

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Reimplement setup for immer using runnables instead of non-OCP in index.ts

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Reimplement setup for MobX strictness using runnables instead of non-OCP in index.ts

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Reimplement setup for application proxy using runnables instead of non-OCP in index.ts

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Reimplement setup for system certifications using runnables instead of non-OCP in index.ts

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Reimplement setup for system shutdown using runnables instead of non-OCP in index.ts

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Reimplement setup for main window visibility using runnables instead of non-OCP in index.ts

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Reimplement setup for application quit using runnables instead of non-OCP in index.ts

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Reimplement setup for after application is ready using runnables instead of non-OCP in index.ts

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Reimplement setup for application tray using runnables instead of non-OCP in index.ts

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Reimplement setup for deep linking using runnables instead of non-OCP in index.ts

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Reimplement setup for root frame using runnables instead of non-OCP in index.ts

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove multiple usages of shared global state

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove recently reimplemented stuff from index.ts

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Extract implementation for intent over technical phenomena (here electron events)

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Consolidate naming of event timing window

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Move directories for timing windows among peers

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Consolidate event naming

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Introduce function to remove duplication from things that are startable and stoppable

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Consolidate implementation of something startable and stoppable

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Consolidate naming

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Consolidate more startables and stoppables

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Introduce abstractions for electron application events

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce more abstractions for electron

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Abstract even more Electron specifics to make them overridable in unit tests

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Override dependency for causing side effects

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make running of many delegatees able to be hierarchical

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Bump async-fn

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make startable-stoppable also restartable

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Extract "isIntegrationTesting" as dependency for having a side-effect

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make temporal dependency apparent

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate all setupping related to single feature to same runnable

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Abstract command line arguments to make them overridable in tests

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Deprecate some globals

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce injectable for __static directory to eliminate side effect on import

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Extract responsibilities from electron

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Extract async initialization from sync-injectable

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Accumulate more global overrides

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make lifecycle of injectable store work as expected

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Extract responsibilities to delegatees

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Rename delegate for accuracy

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Rename another delegate for accuracy

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Rename yet another delegate for accuracy

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Add new global overrides

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Consolidate variable naming

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Rename injectable for accuracy

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make difference between soft and hard quit of application apparent

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make quit triggered by auto update manifest as hard quit instead of just soft quit

Soft quit is the stand-by quit where only the renderer quits. Hard quit is the full quit of also main.

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Introduce abstraction for publishing and subscribing between processes

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Revert "Introduce abstraction for publishing and subscribing between processes"

This reverts commit 46f2d5a5f28bddcf5ffe124b1c590b19e7b9a15f.

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Adapt code and unit tests to recent changes in application setup and event handling

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Group overrides by category

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Abstract event of application activation from electron

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Abstract event of device shutdown from electron

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate runnables related to Electron

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make startableStoppables have ID for better error logging

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make navigating to Helm Charts not blow up

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Add general override for behavioural tests

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Update snapshot

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Adapt vars after merge

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix code style

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix error about multiple states for React Router being present at once

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Kludge around test setup which is difficult because of circular dependencies

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix setupping of sentry after rebase

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Ensure that LensProxy is setupped before starting the main window

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove redundant import

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate to use injectable instead of var for static file directory

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Deprecate another var with injectable

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak timing of runnables

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Extract tray icon path as injectable

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make splash screen not blow up by providing compile-time environment variables

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce a way to run many synchronous runnables

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make runnables for before application is ready synchronous as this is technical limitation of Electron

See: https://github.com/electron/electron/issues/21370

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Kill dead code

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix application quit by calling Electron's event.preventDefault in a very specific way and preventing async

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix missing injectable postfix in file name

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak timeslot of a runnable

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make it possible to not stop something that was never started

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make quit of hidden Lens not blow up

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Extract responsibilities of WindowManager as injectables

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate code for application window

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Simplify directory structures for runnables

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Flag injectable causing side-effect

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Extract helpers for testing promises to test-utils

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make script for running unit tests support targeting

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make startable stoppable support async starting

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce reactive way to get theme from operating system

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Update yarn.lock after rebase

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix code style

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove code-style changes that are not welcome

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using dependency over using explicit side-effect

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Simplify naming

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Kill dead code

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add empty mocks comply to lint

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove global state

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Extract responsibility of setting dependencies to own injectable

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Flag injectable causing side effects

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Extract responsibility to injectable

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak typing

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak naming of runnables

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Revert code-style changes

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak types

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Extract injectable with side-effect for exact overriding

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak name of timeslot to make it more apparent that new content for application load can be added

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak name of the splash window for loading to avoid confusion with similarly named phenomenon

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak comment

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Rename injectable for brevity

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate LensProxy related stuff to directory

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Rename injectables for accuracy

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove usage of extension for injectable for being YAGNI

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Prevent restarting a startableStoppable when already being started

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Rename callback in ApplicationBuilder for accuracy

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix conflicts after rebase

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix merge conflicts

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Revert switch to react-router-dom by installing history as dependency

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make test more strict

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make runnable give friendly error about incorrect hierarchy for easier debugging

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix code-style

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix code style

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using dependency instead of legacy-di

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix timing of injects

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

Co-authored-by: Iku-turso <mikko.aspiala@gmail.com>
This commit is contained in:
Janne SavolainenandMikko Aspiala authored and GitHub committed 2022-05-18 16:18:02 +03:00
1 parent b57d48e39e
commit 54b92aa9b6
302 files changed
+6388 -2025

No files matched your search

@@ -28,6 +28,7 @@ import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.i
import { computed } from "mobx";
import ipcRendererInjectable from "../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
import { UserStore } from "../../../common/user-store";
import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable";
mockWindow();
jest.mock("electron", () => ({
@@ -88,15 +89,16 @@ describe("<Catalog />", () => {
let catalogEntityItem: MockCatalogEntity;
let render: DiRender;
beforeEach(async () => {
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(broadcastMessageInjectable, () => async () => {});
di.permitSideEffects(getConfigurationFileModelInjectable);
di.permitSideEffects(appVersionInjectable);
await di.runSetups();
mockFs();
CatalogEntityDetailRegistry.createInstance();
@@ -58,7 +58,7 @@ describe("Extensions", () => {
let extensionInstallationStateStore: ExtensionInstallationStateStore;
let render: DiRender;
beforeEach(async () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
@@ -71,8 +71,6 @@ describe("Extensions", () => {
"some-directory-for-user-data": {},
});
await di.runSetups();
render = renderFor(di);
installFromInput = jest.fn();
@@ -79,6 +79,8 @@ class NonInjectedHelmCharts extends Component<Dependencies> {
render() {
return (
<SiblingsInTabLayout>
<div data-testid="page-for-helm-charts" style={{ display: "none" }}/>
<ItemListLayout
isConfigurable
tableId="helm_charts"
@@ -6,16 +6,15 @@ import { getInjectable } from "@ogre-tools/injectable";
import { asyncComputed } from "@ogre-tools/injectable-react";
import namespaceStoreInjectable from "../+namespaces/store.injectable";
import { listReleases } from "../../../common/k8s-api/endpoints/helm-releases.api";
import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable";
const releasesInjectable = getInjectable({
id: "releases",
instantiate: (di) => {
const clusterContext = di.inject(clusterFrameContextInjectable);
const namespaceStore = di.inject(namespaceStoreInjectable);
// TODO: Inject clusterContext directly instead of accessing dependency of a dependency
const clusterContext = namespaceStore.context;
return asyncComputed(async () => {
const contextNamespaces = namespaceStore.contextNamespaces || [];
@@ -44,7 +44,7 @@ const extensionPreferenceItemRegistratorInjectable = getInjectable({
}),
);
injectables.forEach(di.register);
di.register(...injectables);
return;
},
@@ -44,7 +44,7 @@ const extensionTelemetryPreferenceItemRegistratorInjectable = getInjectable({
}),
);
injectables.forEach(di.register);
di.register(...injectables);
return;
},
@@ -17,7 +17,7 @@ import ipcRendererInjectable from "../../../../app-paths/get-value-from-register
describe("ClusterRoleBindingDialog tests", () => {
let render: DiRender;
beforeEach(async () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(storesAndApisCanBeCreatedInjectable, () => true);
@@ -26,8 +26,6 @@ describe("ClusterRoleBindingDialog tests", () => {
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
await di.runSetups();
render = renderFor(di);
const store = di.inject(clusterRoleStoreInjectable);
@@ -18,7 +18,7 @@ import ipcRendererInjectable from "../../../../app-paths/get-value-from-register
describe("RoleBindingDialog tests", () => {
let render: DiRender;
beforeEach(async () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(storesAndApisCanBeCreatedInjectable, () => true);
@@ -28,8 +28,6 @@ describe("RoleBindingDialog tests", () => {
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
await di.runSetups();
render = renderFor(di);
const store = di.inject(clusterRoleStoreInjectable);
@@ -30,7 +30,7 @@ describe("<Welcome/>", () => {
let di: DiContainer;
let welcomeBannersStub: WelcomeBannerRegistration[];
beforeEach(async () => {
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
render = renderFor(di);
welcomeBannersStub = [];
@@ -37,7 +37,7 @@ const tolerations: Toleration[] =[
describe("<PodTolerations />", () => {
let render: DiRender;
beforeEach(async () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForLensLocalStorageInjectable, () => "some-directory-for-lens-local-storage" );
@@ -3,11 +3,11 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { ClusterFrameHandler } from "./lens-views";
import { ClusterFrameHandler } from "./cluster-frame-handler";
const clusterFramesInjectable = getInjectable({
id: "cluster-frames",
const clusterFrameHandlerInjectable = getInjectable({
id: "cluster-frame-handler",
instantiate: () => new ClusterFrameHandler(),
});
export default clusterFramesInjectable;
export default clusterFrameHandlerInjectable;
@@ -9,7 +9,7 @@ import type { IComputedValue } from "mobx";
import { computed, makeObservable, reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { ClusterStatus } from "./cluster-status";
import type { ClusterFrameHandler } from "./lens-views";
import type { ClusterFrameHandler } from "./cluster-frame-handler";
import type { Cluster } from "../../../common/cluster/cluster";
import { ClusterStore } from "../../../common/cluster-store/cluster-store";
import { requestClusterActivation } from "../../ipc";
@@ -17,7 +17,7 @@ import { withInjectables } from "@ogre-tools/injectable-react";
import type { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
import clusterViewRouteParametersInjectable from "./cluster-view-route-parameters.injectable";
import clusterFramesInjectable from "./lens-views.injectable";
import clusterFrameHandlerInjectable from "./cluster-frame-handler.injectable";
import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
@@ -107,7 +107,7 @@ export const ClusterView = withInjectables<Dependencies>(NonInjectedClusterView,
getProps: (di) => ({
clusterId: di.inject(clusterViewRouteParametersInjectable).clusterId,
navigateToCatalog: di.inject(navigateToCatalogInjectable),
clusterFrames: di.inject(clusterFramesInjectable),
clusterFrames: di.inject(clusterFrameHandlerInjectable),
entityRegistry: di.inject(catalogEntityRegistryInjectable),
}),
});
@@ -4,23 +4,28 @@
*/
import "@testing-library/jest-dom/extend-expect";
import { KubeConfig } from "@kubernetes/client-node";
import { fireEvent, screen } from "@testing-library/react";
import { fireEvent } from "@testing-library/react";
import type { RenderResult } from "@testing-library/react";
import mockFs from "mock-fs";
import React from "react";
import * as selectEvent from "react-select-event";
import type { Cluster } from "../../../../common/cluster/cluster";
import { DeleteClusterDialog } from "../view";
import type { ClusterModel } from "../../../../common/cluster-types";
import { getDisForUnitTesting } from "../../../../test-utils/get-dis-for-unit-testing";
import { createClusterInjectionToken } from "../../../../common/cluster/create-cluster-injection-token";
import createContextHandlerInjectable from "../../../../main/context-handler/create-context-handler.injectable";
import type { DiRender } from "../../test-utils/renderFor";
import { renderFor } from "../../test-utils/renderFor";
import type { OpenDeleteClusterDialog } from "../open.injectable";
import openDeleteClusterDialogInjectable from "../open.injectable";
import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable";
import createKubeconfigManagerInjectable from "../../../../main/kubeconfig-manager/create-kubeconfig-manager.injectable";
import ipcRendererInjectable from "../../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
import type { ApplicationBuilder } from "../../test-utils/get-application-builder";
import { getApplicationBuilder } from "../../test-utils/get-application-builder";
import { routeInjectionToken } from "../../../../common/front-end-routing/route-injection-token";
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import { routeSpecificComponentInjectionToken } from "../../../routes/route-specific-component-injection-token";
import { navigateToRouteInjectionToken } from "../../../../common/front-end-routing/navigate-to-route-injection-token";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
jest.mock("electron", () => ({
app: {
@@ -38,33 +43,29 @@ jest.mock("electron", () => ({
},
}));
const kubeconfig = `
const multiClusterConfig = `
apiVersion: v1
clusters:
- cluster:
server: https://localhost
name: test
name: some-current-context-cluster
- cluster:
server: http://localhost
name: other-cluster
name: some-non-current-context-cluster
contexts:
- context:
cluster: test
user: test
name: test
cluster: some-current-context-cluster
user: some-user
name: some-current-context
- context:
cluster: test
user: test
name: test2
- context:
cluster: other-cluster
user: test
name: other-context
current-context: other-context
cluster: some-non-current-context-cluster
user: some-user
name: some-non-current-context
current-context: some-current-context
kind: Config
preferences: {}
users:
- name: test
- name: some-user
user:
token: kubeconfig-user-q4lm4:xxxyyyy
`;
@@ -74,17 +75,17 @@ apiVersion: v1
clusters:
- cluster:
server: http://localhost
name: other-cluster
name: some-cluster
contexts:
- context:
cluster: other-cluster
user: test
name: other-context
current-context: other-context
cluster: some-cluster
user: some-user
name: some-context
current-context: some-context
kind: Config
preferences: {}
users:
- name: test
- name: some-user
user:
token: kubeconfig-user-q4lm4:xxxyyyy
`;
@@ -92,60 +93,107 @@ users:
let config: KubeConfig;
describe("<DeleteClusterDialog />", () => {
let applicationBuilder: ApplicationBuilder;
let createCluster: (model: ClusterModel) => Cluster;
let openDeleteClusterDialog: OpenDeleteClusterDialog;
let render: DiRender;
beforeEach(async () => {
const { mainDi, rendererDi, runSetups } = getDisForUnitTesting({ doGeneralOverrides: true });
applicationBuilder = getApplicationBuilder();
applicationBuilder.beforeApplicationStart(({ mainDi, rendererDi }) => {
mainDi.override(createContextHandlerInjectable, () => () => undefined as any);
mainDi.override(createKubeconfigManagerInjectable, () => () => undefined as any);
rendererDi.override(hotbarStoreInjectable, () => ({}));
rendererDi.override(storesAndApisCanBeCreatedInjectable, () => true);
});
const { rendererDi } = applicationBuilder.dis;
rendererDi.register(testRouteInjectable, testRouteComponentInjectable);
applicationBuilder.beforeRender(({ rendererDi }) => {
const navigateToRoute = rendererDi.inject(navigateToRouteInjectionToken);
const testRoute = rendererDi.inject(testRouteInjectable);
navigateToRoute(testRoute);
});
render = renderFor(rendererDi);
mainDi.override(createContextHandlerInjectable, () => () => undefined);
mainDi.override(createKubeconfigManagerInjectable, () => () => undefined);
mockFs();
rendererDi.override(storesAndApisCanBeCreatedInjectable, () => true);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
await runSetups();
openDeleteClusterDialog = rendererDi.inject(openDeleteClusterDialogInjectable);
createCluster = mainDi.inject(createClusterInjectionToken);
applicationBuilder.beforeRender(({ rendererDi }) => {
openDeleteClusterDialog = rendererDi.inject(openDeleteClusterDialogInjectable);
createCluster = rendererDi.inject(createClusterInjectionToken);
});
});
afterEach(() => {
mockFs.restore();
});
it("shows context switcher when deleting current cluster", async () => {
const mockOpts = {
"temp-kube-config": multiClusterConfig,
};
mockFs(mockOpts);
config = new KubeConfig();
config.loadFromString(multiClusterConfig);
applicationBuilder.beforeRender(({ rendererDi }) => {
const createCluster = rendererDi.inject(createClusterInjectionToken);
const cluster = createCluster({
id: "some-current-context-cluster",
contextName: "some-current-context",
preferences: {
clusterName: "some-current-context-cluster",
},
kubeConfigPath: "./temp-kube-config",
});
openDeleteClusterDialog({ cluster, config });
});
const rendered = await applicationBuilder.render();
const { getByText } = rendered;
const menu = getByText("Select new context...");
expect(menu).toBeInTheDocument();
selectEvent.openMenu(menu);
expect(getByText("some-current-context")).toBeInTheDocument();
expect(getByText("some-non-current-context")).toBeInTheDocument();
});
describe("Kubeconfig with different clusters", () => {
let rendered: RenderResult;
beforeEach(async () => {
const mockOpts = {
"temp-kube-config": kubeconfig,
"temp-kube-config": multiClusterConfig,
};
mockFs(mockOpts);
config = new KubeConfig();
config.loadFromString(kubeconfig);
});
config.loadFromString(multiClusterConfig);
afterEach(() => {
mockFs.restore();
rendered = await applicationBuilder.render();
});
it("renders w/o errors", () => {
const { container } = render(<DeleteClusterDialog />);
expect(container).toBeInstanceOf(HTMLElement);
expect(rendered.container).toBeInstanceOf(HTMLElement);
});
it("shows warning when deleting non-current-context cluster", async () => {
it("shows warning when deleting non-current-context cluster", () => {
const cluster = createCluster({
id: "test",
contextName: "test",
id: "some-non-current-context-cluster",
contextName: "some-non-current-context",
preferences: {
clusterName: "minikube",
},
@@ -154,99 +202,78 @@ describe("<DeleteClusterDialog />", () => {
openDeleteClusterDialog({ cluster, config });
render(<DeleteClusterDialog />);
const message = "The contents of kubeconfig file will be changed!";
expect(await screen.findByText(message)).toBeInstanceOf(HTMLElement);
expect(rendered.getByText(message)).toBeInstanceOf(HTMLElement);
});
it("shows warning when deleting current-context cluster", async () => {
it("shows warning when deleting current-context cluster", () => {
const cluster = createCluster({
id: "other-cluster",
contextName: "other-context",
id: "some-current-context-cluster",
contextName: "some-current-context",
preferences: {
clusterName: "other-cluster",
clusterName: "some-current-context-cluster",
},
kubeConfigPath: "./temp-kube-config",
});
openDeleteClusterDialog({ cluster, config });
render(<DeleteClusterDialog />);
expect(await screen.findByTestId("current-context-warning")).toBeInstanceOf(HTMLElement);
expect(rendered.getByTestId("current-context-warning")).toBeInstanceOf(HTMLElement);
});
it("shows context switcher when deleting current cluster", async () => {
it("shows context switcher after checkbox click", () => {
const cluster = createCluster({
id: "other-cluster",
contextName: "other-context",
id: "some-current-context-cluster",
contextName: "some-current-context",
preferences: {
clusterName: "other-cluster",
clusterName: "some-current-context-cluster",
},
kubeConfigPath: "./temp-kube-config",
});
openDeleteClusterDialog({ cluster, config });
render(<DeleteClusterDialog />);
const menu = await screen.findByText("Select new context...");
expect(menu).toBeInTheDocument();
selectEvent.openMenu(menu);
expect(await screen.findByText("test")).toBeInTheDocument();
expect(await screen.findByText("test2")).toBeInTheDocument();
});
it("shows context switcher after checkbox click", async () => {
const cluster = createCluster({
id: "some-cluster",
contextName: "test",
preferences: {
clusterName: "test",
},
kubeConfigPath: "./temp-kube-config",
});
openDeleteClusterDialog({ cluster, config });
render(<DeleteClusterDialog />);
const link = await screen.findByTestId("context-switch");
const { getByText, getByTestId } = rendered;
const link = getByTestId("context-switch");
expect(link).toBeInstanceOf(HTMLElement);
fireEvent.click(link);
const menu = await screen.findByText("Select new context...");
const menu = getByText("Select new context...");
expect(menu).toBeInTheDocument();
selectEvent.openMenu(menu);
expect(await screen.findByText("test")).toBeInTheDocument();
expect(await screen.findByText("test2")).toBeInTheDocument();
expect(getByText("some-current-context")).toBeInTheDocument();
expect(getByText("some-non-current-context")).toBeInTheDocument();
});
it("shows warning for internal kubeconfig cluster", async () => {
it("given cluster in internal kubeconfig, when deleting cluster outside of current context, shows warning for internal kubeconfig cluster", () => {
const cluster = createCluster({
id: "some-cluster",
contextName: "test",
id: "some-non-current-context-cluster",
contextName: "some-non-current-context",
preferences: {
clusterName: "test",
clusterName: "some-non-current-context-cluster",
},
kubeConfigPath: "./temp-kube-config",
});
const spy = jest.spyOn(cluster, "isInLocalKubeconfig").mockImplementation(() => true);
openDeleteClusterDialog({ cluster, config });
render(<DeleteClusterDialog />);
expect(await screen.findByTestId("internal-kubeconfig-warning")).toBeInstanceOf(HTMLElement);
expect(rendered.getByTestId("internal-kubeconfig-warning")).toBeInstanceOf(HTMLElement);
spy.mockRestore();
});
});
describe("Kubeconfig with single cluster", () => {
let rendered: RenderResult;
beforeEach(async () => {
const mockOpts = {
"temp-kube-config": singleClusterConfig,
@@ -256,26 +283,46 @@ describe("<DeleteClusterDialog />", () => {
config = new KubeConfig();
config.loadFromString(singleClusterConfig);
rendered = await applicationBuilder.render();
});
afterEach(() => {
mockFs.restore();
});
it("shows warning if no other contexts left", async () => {
it("shows warning if no other contexts left", () => {
const cluster = createCluster({
id: "other-cluster",
contextName: "other-context",
id: "some-cluster",
contextName: "some-context",
preferences: {
clusterName: "other-cluster",
clusterName: "some-cluster",
},
kubeConfigPath: "./temp-kube-config",
});
openDeleteClusterDialog({ cluster, config });
render(<DeleteClusterDialog />);
expect(await screen.findByTestId("no-more-contexts-warning")).toBeInstanceOf(HTMLElement);
expect(rendered.getByTestId("no-more-contexts-warning")).toBeInstanceOf(HTMLElement);
});
});
});
const testRouteInjectable = getInjectable({
id: "some-test-route",
instantiate: () => ({
path: "/some-test-path",
clusterFrame: false,
isEnabled: computed(() => true),
}),
injectionToken: routeInjectionToken,
});
const testRouteComponentInjectable = getInjectable({
id: "some-test-component",
instantiate: (di) => ({
route: di.inject(testRouteInjectable),
Component: () => <DeleteClusterDialog />,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
@@ -22,7 +22,7 @@ const initialTabs: DockTab[] = [
describe("DockStore", () => {
let dockStore: DockStore;
beforeEach(async () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(hostedClusterIdInjectable, () => "some-cluster-id");
@@ -91,8 +91,6 @@ describe("<DockTabs />", () => {
di.permitSideEffects(getConfigurationFileModelInjectable);
di.permitSideEffects(appVersionInjectable);
await di.runSetups();
dockStore = di.inject(dockStoreInjectable);
await dockStore.whenReady;
@@ -5,20 +5,21 @@
import { getInjectable } from "@ogre-tools/injectable";
import path from "path";
import { hasCorrectExtension } from "./has-correct-extension";
import { staticFilesDirectory } from "../../../../common/vars";
import readFileInjectable from "../../../../common/fs/read-file.injectable";
import readDirInjectable from "../../../../common/fs/read-dir.injectable";
import type { RawTemplates } from "./create-resource-templates.injectable";
import type { GetAbsolutePath } from "../../../../common/path/get-absolute-path.injectable";
import getAbsolutePathInjectable from "../../../../common/path/get-absolute-path.injectable";
import staticFilesDirectoryInjectable from "../../../../common/vars/static-files-directory.injectable";
interface Dependencies {
readDir: (dirPath: string) => Promise<string[]>;
readFile: (filePath: string) => Promise<string>;
getAbsolutePath: GetAbsolutePath;
staticFilesDirectory: string;
}
async function getTemplates({ readDir, readFile, getAbsolutePath }: Dependencies) {
async function getTemplates({ readDir, readFile, getAbsolutePath, staticFilesDirectory }: Dependencies) {
const templatesFolder = getAbsolutePath(staticFilesDirectory, "../templates/create-resource");
/**
@@ -43,6 +44,7 @@ const lensCreateResourceTemplatesInjectable = getInjectable({
readFile: di.inject(readFileInjectable),
readDir: di.inject(readDirInjectable),
getAbsolutePath: di.inject(getAbsolutePathInjectable),
staticFilesDirectory: di.inject(staticFilesDirectoryInjectable),
});
return ["lens", templates];
@@ -127,7 +127,7 @@ const getFewPodsTabData = (tabId: TabId, deps: Partial<LogTabViewModelDependenci
describe("<LogResourceSelector />", () => {
let render: DiRender;
beforeEach(async () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
@@ -142,8 +142,6 @@ describe("<LogResourceSelector />", () => {
render = renderFor(di);
await di.runSetups();
mockFs({
"tmp": {},
});
@@ -60,12 +60,10 @@ const getOnePodViewModel = (tabId: TabId, deps: Partial<LogTabViewModelDependenc
describe("LogSearch tests", () => {
let render: DiRender;
beforeEach(async () => {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
render = renderFor(di);
await di.runSetups();
});
it("renders w/o errors", () => {
@@ -63,7 +63,7 @@ describe("<HotbarRemoveCommand />", () => {
mockFs.restore();
});
it("renders w/o errors", async () => {
it("renders w/o errors", () => {
di.override(hotbarStoreInjectable, () => ({
hotbars: [mockHotbars["1"]],
getById: (id: string) => mockHotbars[id],
@@ -73,14 +73,12 @@ describe("<HotbarRemoveCommand />", () => {
getDisplayLabel: () => "1: Default",
}) as unknown as HotbarStore);
await di.runSetups();
const { container } = render(<HotbarRemoveCommand />);
expect(container).toBeInstanceOf(HTMLElement);
});
it("calls remove if you click on the entry", async () => {
it("calls remove if you click on the entry", () => {
const removeMock = jest.fn();
di.override(hotbarStoreInjectable, () => ({
@@ -91,8 +89,6 @@ describe("<HotbarRemoveCommand />", () => {
getDisplayLabel: () => "1: Default",
}) as unknown as HotbarStore);
await di.runSetups();
const { getByText } = render(
<>
<HotbarRemoveCommand />
@@ -24,10 +24,11 @@ import type { ThemeStore } from "../../themes/store";
import { MenuActions } from "../menu/menu-actions";
import { MenuItem } from "../menu";
import { Checkbox } from "../checkbox";
import { UserStore } from "../../../common/user-store";
import type { UserStore } from "../../../common/user-store";
import type { ItemListStore } from "./list-layout";
import { withInjectables } from "@ogre-tools/injectable-react";
import themeStoreInjectable from "../../themes/store.injectable";
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
import pageFiltersStoreInjectable from "./page-filters/store.injectable";
import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable";
import openConfirmDialogInjectable from "../confirm-dialog/open.injectable";
@@ -69,6 +70,7 @@ export interface ItemListLayoutContentProps<Item extends ItemObject, PreLoadStor
interface Dependencies {
themeStore: ThemeStore;
userStore: UserStore;
pageFiltersStore: PageFiltersStore;
openConfirmDialog: OpenConfirmDialog;
}
@@ -333,7 +335,7 @@ class NonInjectedItemListLayoutContent<
showColumn({ id: columnId, showWithColumn }: TableCellProps): boolean {
const { tableId, isConfigurable } = this.props;
return !isConfigurable || !tableId || !UserStore.getInstance().isTableColumnHidden(tableId, columnId, showWithColumn);
return !isConfigurable || !tableId || !this.props.userStore.isTableColumnHidden(tableId, columnId, showWithColumn);
}
renderColumnVisibilityMenu() {
@@ -356,7 +358,7 @@ class NonInjectedItemListLayoutContent<
value={this.showColumn(cellProps)}
onChange={(
tableId
? (() => cellProps.id && UserStore.getInstance().toggleTableColumnVisibility(tableId, cellProps.id))
? (() => cellProps.id && this.props.userStore.toggleTableColumnVisibility(tableId, cellProps.id))
: undefined
)}
/>
@@ -373,6 +375,7 @@ export const ItemListLayoutContent = withInjectables<Dependencies, ItemListLayou
getProps: (di, props) => ({
...props,
themeStore: di.inject(themeStoreInjectable),
userStore: di.inject(userStoreInjectable),
pageFiltersStore: di.inject(pageFiltersStoreInjectable),
openConfirmDialog: di.inject(openConfirmDialogInjectable),
}),
@@ -57,7 +57,7 @@ describe("kube-object-menu", () => {
let di: DiContainer;
let render: DiRender;
beforeEach(async () => {
beforeEach(() => {
const MenuItemComponent = () => <li>Some menu item</li>;
const someTestExtension = new SomeTestExtension([
{
@@ -18,7 +18,7 @@ describe("kube-object-status-icon", () => {
let render: DiRender;
let kubeObjectStatusRegistrations: KubeObjectStatusRegistration[];
beforeEach(async () => {
beforeEach(() => {
// TODO: Make mocking of date in unit tests global
global.Date.now = () => new Date("2015-10-21T07:28:00Z").getTime();
@@ -35,8 +35,6 @@ describe("kube-object-status-icon", () => {
di.override(rendererExtensionsInjectable, () =>
computed(() => [someTestExtension]),
);
await di.runSetups();
});
it("given no statuses, when rendered, renders as empty", () => {
@@ -45,7 +45,7 @@ export const TabLayout = observer(({ className, contentClass, tabs = [], childre
key={url}
label={title}
value={url}
active={!!matchPath(currentLocation, { path: routePath, exact })}
active={!!matchPath(currentLocation, { path: routePath, exact })}
/>
))}
</Tabs>
@@ -76,15 +76,13 @@ describe("<TopBar/>", () => {
let di: DiContainer;
let render: DiRender;
beforeEach(async () => {
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
mockFs();
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
await di.runSetups();
render = renderFor(di);
});
@@ -50,6 +50,7 @@ class NonInjectedMenuActions extends React.Component<MenuActionsProps & Dependen
removeConfirmationMessage: "Remove item?",
};
// TODO: Make deterministic
public id = uniqueId("menu_actions_");
@observable isOpen = !!this.props.toolbar;
@@ -30,7 +30,7 @@ describe("<Select />", () => {
let di: DiContainer;
let render: DiRender;
beforeEach(async () => {
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
render = renderFor(di);
@@ -39,15 +39,13 @@ describe("<StatusBar />", () => {
let di: DiContainer;
let statusBarItems: IObservableArray<any>;
beforeEach(async () => {
beforeEach(() => {
statusBarItems = observable.array([]);
di = getDiForUnitTesting({ doGeneralOverrides: true });
render = renderFor(di);
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(rendererExtensionsInjectable, () => computed(() => [new SomeTestExtension(statusBarItems)]));
await di.runSetups();
});
it("renders w/o errors", () => {
@@ -9,7 +9,6 @@ import { extensionRegistratorInjectionToken } from "../../../extensions/extensio
import type { IObservableArray } from "mobx";
import { computed, observable, runInAction } from "mobx";
import { renderFor } from "./renderFor";
import observableHistoryInjectable from "../../navigation/observable-history.injectable";
import React from "react";
import { Router } from "react-router";
import { Observer } from "mobx-react";
@@ -18,7 +17,6 @@ import allowedResourcesInjectable from "../../../common/cluster-store/allowed-re
import type { RenderResult } from "@testing-library/react";
import { fireEvent } from "@testing-library/react";
import type { KubeResource } from "../../../common/rbac";
import directoryForLensLocalStorageInjectable from "../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
import { Sidebar } from "../layout/sidebar";
import { getDisForUnitTesting } from "../../../test-utils/get-dis-for-unit-testing";
import type { DiContainer } from "@ogre-tools/injectable";
@@ -34,6 +32,18 @@ import type { MenuItemOpts } from "../../../main/menu/application-menu-items.inj
import applicationMenuItemsInjectable from "../../../main/menu/application-menu-items.injectable";
import type { MenuItem, MenuItemConstructorOptions } from "electron";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import navigateToHelmChartsInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable";
import hostedClusterInjectable from "../../../common/cluster-store/hosted-cluster.injectable";
import { ClusterFrameContext } from "../../cluster-frame-context/cluster-frame-context";
import type { Cluster } from "../../../common/cluster/cluster";
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable";
import startMainApplicationInjectable from "../../../main/start-main-application/start-main-application.injectable";
import startFrameInjectable from "../../start-frame/start-frame.injectable";
import { flushPromises } from "../../../common/test-utils/flush-promises";
import type { NamespaceStore } from "../+namespaces/store";
import namespaceStoreInjectable from "../+namespaces/store.injectable";
import historyInjectable from "../../navigation/history.injectable";
type Callback = (dis: DiContainers) => void | Promise<void>;
@@ -42,12 +52,12 @@ export interface ApplicationBuilder {
setEnvironmentToClusterFrame: () => ApplicationBuilder;
addExtensions: (...extensions: LensRendererExtension[]) => Promise<ApplicationBuilder>;
allowKubeResource: (resourceName: KubeResource) => ApplicationBuilder;
beforeSetups: (callback: Callback) => ApplicationBuilder;
beforeApplicationStart: (callback: Callback) => ApplicationBuilder;
beforeRender: (callback: Callback) => ApplicationBuilder;
render: () => Promise<RenderResult>;
applicationMenu: {
click: (path: string) => void;
click: (path: string) => Promise<void>;
};
preferences: {
@@ -57,6 +67,10 @@ export interface ApplicationBuilder {
click: (id: string) => void;
};
};
helmCharts: {
navigate: () => void;
};
}
interface DiContainers {
@@ -70,13 +84,14 @@ interface Environment {
}
export const getApplicationBuilder = () => {
const { rendererDi, mainDi, runSetups } = getDisForUnitTesting({
const { rendererDi, mainDi } = getDisForUnitTesting({
doGeneralOverrides: true,
});
const dis = { rendererDi, mainDi };
const clusterStoreStub = {
provideInitialFromMain: () => {},
getById: (): null => null,
} as unknown as ClusterStore;
@@ -84,7 +99,7 @@ export const getApplicationBuilder = () => {
rendererDi.override(storesAndApisCanBeCreatedInjectable, () => true);
mainDi.override(clusterStoreInjectable, () => clusterStoreStub);
const beforeSetupsCallbacks: Callback[] = [];
const beforeApplicationStartCallbacks: Callback[] = [];
const beforeRenderCallbacks: Callback[] = [];
const extensionsState = observable.array<LensRendererExtension>();
@@ -130,7 +145,7 @@ export const getApplicationBuilder = () => {
dis,
applicationMenu: {
click: (path: string) => {
click: async (path: string) => {
const applicationMenuItems = mainDi.inject(
applicationMenuItemsInjectable,
);
@@ -160,6 +175,8 @@ export const getApplicationBuilder = () => {
undefined,
{},
);
await flushPromises();
},
},
@@ -199,6 +216,14 @@ export const getApplicationBuilder = () => {
},
},
helmCharts: {
navigate: () => {
const navigateToHelmCharts = rendererDi.inject(navigateToHelmChartsInjectable);
navigateToHelmCharts();
},
},
setEnvironmentToClusterFrame: () => {
environment = environments.clusterFrame;
@@ -208,11 +233,29 @@ export const getApplicationBuilder = () => {
computed(() => new Set([...allowedResourcesState])),
);
rendererDi.override(
directoryForLensLocalStorageInjectable,
() => "/irrelevant",
const clusterStub = {
accessibleNamespaces: [],
} as unknown as Cluster;
const namespaceStoreStub = {
contextNamespaces: [],
} as unknown as NamespaceStore;
const clusterFrameContextFake = new ClusterFrameContext(
clusterStub,
{
namespaceStore: namespaceStoreStub,
},
);
rendererDi.override(namespaceStoreInjectable, () => namespaceStoreStub);
rendererDi.override(hostedClusterInjectable, () => clusterStub);
rendererDi.override(clusterFrameContextInjectable, () => clusterFrameContextFake);
// Todo: get rid of global state.
KubeObjectStore.defaultContext.set(clusterFrameContextFake);
return builder;
},
@@ -254,8 +297,8 @@ export const getApplicationBuilder = () => {
return builder;
},
beforeSetups(callback: (dis: DiContainers) => void) {
beforeSetupsCallbacks.push(callback);
beforeApplicationStart(callback: (dis: DiContainers) => void) {
beforeApplicationStartCallbacks.push(callback);
return builder;
},
@@ -267,14 +310,20 @@ export const getApplicationBuilder = () => {
},
async render() {
for (const callback of beforeSetupsCallbacks) {
for (const callback of beforeApplicationStartCallbacks) {
await callback(dis);
}
await runSetups();
const startMainApplication = mainDi.inject(startMainApplicationInjectable);
await startMainApplication();
const startFrame = rendererDi.inject(startFrameInjectable);
await startFrame();
const render = renderFor(rendererDi);
const history = rendererDi.inject(observableHistoryInjectable);
const history = rendererDi.inject(historyInjectable);
const currentRouteComponent = rendererDi.inject(currentRouteComponentInjectable);
for (const callback of beforeRenderCallbacks) {