mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Refactor and transform "Application update", "Preferences" and "Application menu" into Features (#6437)
* Move some code for application update to feature directory The rest of the code could not be moved yet because of work-in-progress refactorings for OCP compliance. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce helper to get a global override for a function-injectable Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract global override-files from bloated getDiForUnitTesting Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Move some more code for application update to feature directory Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce competition for top bar items to achieve OCP-compliance Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract top menu item for opening context menu using the new competition Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract top menu item for navigating to home as OCP Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract top menu item for navigating to back as OCP Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract top menu item for navigating to forward as OCP Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract top menu item for application update as OCP Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Format code to make ongoing refactoring a bit easier Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Add missing unit tests for top bar extendability using extension API This makes ongoing refactoring easier. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Replace implementation for old top-bar items for losing competition Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract top menu item for window controls as OCP Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce reusable component for spacing between other components Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce helper component to render list of React elements Features: - Placeholder for empty list - Separators between items - No boilerplate for "key" prop in React Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix double-clicking and dragging of window from top bar Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update snapshots Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix stuff broken in rebase Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Update snapshots after rebase Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make fake time have a default value for "now" Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Migrate some application menu items to injectables Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Consolidate separators of application menu in single file Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove duplication from separators in application menu Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract some operation system actions from application menu as injectables Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract menu item for quitting application as injectable Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce way to type narrow a string property Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make the di's for unit testing able to auto-register also named exports Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make global override less strict to simplify setup of many unit tests There's a better solution for this in the horizon, as this overridden thing is better faked than stubbed. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify a test Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce way to create hierarchical composites from a flat array Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Implement hierarchy of application menu items using "many-root" composite Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Migrate more application menu items to injectables Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Simplify hierarchy of application menu items using "single-root" composite Also solve composed typing of application menu by using Discriminated Unions of TypeScript, see: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update snapshot Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify creation of composite Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make composite able to have custom handler for missing parent ids This will be useful next for application menu items, where a missing parent id cannot be fatal. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Defend against self-referencing composites Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Reintroduce non-fatal handling of orphan application menu items Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make faked, yet weak, typing a bit stronger Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify getting of composite paths Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make registrator for application menu items support all known scenarios Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Add logging for unrecognizable application menu item types Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Rename id of menu item to keep it discoverable by existing extensions Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Consolidate code to check for updates closer to feature Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce reusable horizontal line Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for preferences as a Feature Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for terminal preference tab Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for editor preference tab Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for proxy preference tab Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for telemetry preference tab Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for application preference tab Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move code related to helm chart preferences under related Feature in preparation for competition Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for kubernetes preference tab Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using competition for application preferences Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using competition for editor preferences Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using competition for kubernetes preferences Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using competition for proxy preferences Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing observer to make sure component updates Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using competition for telemetry preferences Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using competition for terminal preferences Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak UI for preferences Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove dead code Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak more UI of preferences Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Revert "Introduce reusable horizontal line" This reverts commit 4d8c147fe0f1a14bd884f73cf345e7d3a28b954a. Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove dead code Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce utility to find exactly one item from array Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for preferences navigation Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move code under a Feature Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove usage of old code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove dead code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add extensions tab group to preferences Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce way to find out if composite has a descendant This will serve eg. hiding of empty preference tab groups. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make tab groups and tabs in preferences not render when there is no content Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove code made redundant with hiding of preference tabs without content Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Tweak UI for preference navigation tab groups Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce test helper to abstract discovery of HTML elements 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> * Consolidate discovery of HTML elements in some tests Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update snapshots Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Adapt application builder to changes in preference navigation Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Adapt test setup to changes in preference navigation Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove uninteresting technical tests that are covered by behavioural ones Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate discovery of HTML elements in a test Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove test ID made redundant by consolidating discovery of HTML elements Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove duplication from preference pages Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix import Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make queries in element discovery return matching attribute values for easier testing Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make element discovery able to do nested discovery Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make element discovery able to discover without value for attribute Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Implement registrator for preference items Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update snapshots Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make styling less brittle by not relying on static HTML-element structures with CSS-rules Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Add todo Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove "group" from preference types, as it is exact replica of "item" Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Update snapshots Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Replace usages of react-component factory with actual components for simplicity Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Start considering application preferences as default tab Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate naming Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make sense in horizontal lines Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Clean public interface of a normalize composite Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate name of function Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate directory structure of composite Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move utility functions to common Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Rename a preference item type to better communicate intent in UI Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move shared UI component to more common place Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate sizing of horizontal line to t-shirts Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move application update related preferences under application update feature Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make winner of competition to use original route for preferences Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make HTML element discovery require less parameters Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Extract "composable-responsibilities" for Discriminable, Labelable, Orderable, and Showable Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Update snapshot Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove dead code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move code under a feature Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate navigating to preferences Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move code under a feature Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Relocate code under a sub-feature Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add TODO Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove dead code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix merge conflicts Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update snapshots after rebase Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix import path after rebase Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove duplication from exhaustiveness checks for discriminating unions Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add TODO Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update link to a more recent article Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify static Showability of a PreferenceItem Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Move general function to general directory Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract responsibility of "separability" Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make separator in Map-component know left and right item Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove additional separators when separated items are not shown for having no content Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Update snapshots Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix lint error Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Adapt integration test to recent changes Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Adapt more integration tests to recent changes Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make composite not care about in formatting of ids Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Adapt application builder and tests to array-like paths over string-like paths Array-like paths do not have weakness for special characters as part of id, such as ".". Also note: the error messaging for clicking of application menu in application builder is a bit worse now I think, but the simplification of the test code is worth it in this case IMHO. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make composite not care about formatting of ids Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Adapt application builder and tests to array-like paths over string-like paths Array-like paths do not have weakness for special characters as part of id, such as ".". Also note: the error messaging for clicking of application menu in application builder is a bit worse now I think, but the simplification of the test code is worth it in this case IMHO. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Consolidate output of get-composite-path to match find-composite's input Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make global overrides for functions log args of the call for devability Also make the thrown error suggest how to fix the problem. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make attempts to log error throw in unit tests Errors cannot be allowed to happen without a unit test explicitly causing it. Errors cannot be allowed to happen without author of unit test knowing it. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make composite not know about how children are transformed Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove some duplication from tests of composite Also make the thrown error suggest how to fix the problem. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make composite unit test an unrealistic test scenario about undefined ids Also make the thrown error suggest how to fix the problem. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Kill dead code Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify unit tests for composite Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Consolidate tests to now point-free composite Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify "Showable" Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make non-shown application menu items not break composite structure This was made apparent by adding related unit tests for all known environments. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify usages of Orderable and Showable Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Extract being maybe Showable as explicit composable responsibility Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make more showables maybe showable Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Consolidate logic for application menu for Windows to be based on composite Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Start using named export for composite Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Rename type for accuracy Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove unnecessary type and value Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make composable responsibilities readonly to nudge towards immutability Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Remove a bit of duplication to create TS-constants Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make a comment and test name make more sense Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify fallthrough in a switch/case Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Replace inline styles with proper CSS Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Switch to correct type to indicate "object which might not contain a property" Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify overriding of platform in a unit test Also make typing of platforms more strict, and remove some magic strings. Also add a TODO for further OCP-ification. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Consolidate some "maybe-types" and arguments using them Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Update snapshots Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
parent
38feab813c
commit
37eb236948
@ -24,8 +24,8 @@ describe("preferences page tests", () => {
|
|||||||
|
|
||||||
await app.evaluate(async ({ app }) => {
|
await app.evaluate(async ({ app }) => {
|
||||||
await app.applicationMenu
|
await app.applicationMenu
|
||||||
.getMenuItemById(process.platform === "darwin" ? "root" : "file")
|
.getMenuItemById(process.platform === "darwin" ? "mac" : "file")
|
||||||
.submenu.getMenuItemById("preferences")
|
.submenu.getMenuItemById("navigate-to-preferences")
|
||||||
.click();
|
.click();
|
||||||
});
|
});
|
||||||
}, 10*60*1000);
|
}, 10*60*1000);
|
||||||
@ -37,7 +37,7 @@ describe("preferences page tests", () => {
|
|||||||
it('shows "preferences" and can navigate through the tabs', async () => {
|
it('shows "preferences" and can navigate through the tabs', async () => {
|
||||||
const pages = [
|
const pages = [
|
||||||
{
|
{
|
||||||
id: "application",
|
id: "app",
|
||||||
header: "Application",
|
header: "Application",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -51,8 +51,8 @@ describe("preferences page tests", () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
for (const { id, header } of pages) {
|
for (const { id, header } of pages) {
|
||||||
await window.click(`[data-testid=tab-link-for-${id}]`);
|
await window.click(`[data-preference-tab-link-test=${id}]`);
|
||||||
await window.waitForSelector(`[data-testid=${id}-header] >> text=${header}`);
|
await window.waitForSelector(`[data-preference-page-title-test] >> text=${header}`);
|
||||||
}
|
}
|
||||||
}, 10*60*1000);
|
}, 10*60*1000);
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ describe("Lens command palette", () => {
|
|||||||
let window: Page;
|
let window: Page;
|
||||||
let cleanup: undefined | (() => Promise<void>);
|
let cleanup: undefined | (() => Promise<void>);
|
||||||
let app: ElectronApplication;
|
let app: ElectronApplication;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
({ window, cleanup, app } = await utils.start());
|
({ window, cleanup, app } = await utils.start());
|
||||||
await utils.clickWelcomeButton(window);
|
await utils.clickWelcomeButton(window);
|
||||||
@ -25,7 +25,7 @@ describe("Lens command palette", () => {
|
|||||||
await app.evaluate(async ({ app }) => {
|
await app.evaluate(async ({ app }) => {
|
||||||
await app.applicationMenu
|
await app.applicationMenu
|
||||||
?.getMenuItemById("view")
|
?.getMenuItemById("view")
|
||||||
?.submenu?.getMenuItemById("command-palette")
|
?.submenu?.getMenuItemById("open-command-palette")
|
||||||
?.click();
|
?.click();
|
||||||
});
|
});
|
||||||
await window.waitForSelector(".Select__option >> text=Hotbar: Switch");
|
await window.waitForSelector(".Select__option >> text=Hotbar: Switch");
|
||||||
|
|||||||
@ -34,7 +34,7 @@ import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
|||||||
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
||||||
import releaseChannelInjectable from "../vars/release-channel.injectable";
|
import releaseChannelInjectable from "../vars/release-channel.injectable";
|
||||||
import defaultUpdateChannelInjectable from "../application-update/selected-update-channel/default-update-channel.injectable";
|
import defaultUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/default-update-channel.injectable";
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import { computed } from "mobx";
|
|
||||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
|
||||||
|
|
||||||
const appPreferencesRouteInjectable = getInjectable({
|
|
||||||
id: "app-preferences-route",
|
|
||||||
|
|
||||||
instantiate: () => ({
|
|
||||||
path: "/preferences/app",
|
|
||||||
clusterFrame: false,
|
|
||||||
isEnabled: computed(() => true),
|
|
||||||
}),
|
|
||||||
|
|
||||||
injectionToken: frontEndRouteInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default appPreferencesRouteInjectable;
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import appPreferencesRouteInjectable from "./app-preferences-route.injectable";
|
|
||||||
import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token";
|
|
||||||
|
|
||||||
const navigateToAppPreferencesInjectable = getInjectable({
|
|
||||||
id: "navigate-to-app-preferences",
|
|
||||||
|
|
||||||
instantiate: (di) => {
|
|
||||||
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
|
|
||||||
const route = di.inject(appPreferencesRouteInjectable);
|
|
||||||
|
|
||||||
return () => navigateToRoute(route);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default navigateToAppPreferencesInjectable;
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import { computed } from "mobx";
|
|
||||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
|
||||||
import type { Route } from "../../../front-end-route-injection-token";
|
|
||||||
|
|
||||||
interface ExtensionPreferenceRouteParams {
|
|
||||||
extensionId: string;
|
|
||||||
tabId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const extensionPreferencesRouteInjectable = getInjectable({
|
|
||||||
id: "extension-preferences-route",
|
|
||||||
|
|
||||||
instantiate: (): Route<ExtensionPreferenceRouteParams> => ({
|
|
||||||
path: "/preferences/extension/:extensionId/:tabId?",
|
|
||||||
clusterFrame: false,
|
|
||||||
isEnabled: computed(() => true),
|
|
||||||
}),
|
|
||||||
|
|
||||||
injectionToken: frontEndRouteInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default extensionPreferencesRouteInjectable;
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import extensionPreferencesRouteInjectable from "./extension-preferences-route.injectable";
|
|
||||||
import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token";
|
|
||||||
|
|
||||||
const navigateToExtensionPreferencesInjectable = getInjectable({
|
|
||||||
id: "navigate-to-extension-preferences",
|
|
||||||
|
|
||||||
instantiate: (di) => {
|
|
||||||
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
|
|
||||||
const route = di.inject(extensionPreferencesRouteInjectable);
|
|
||||||
|
|
||||||
return (extensionId: string, tabId?: string) => navigateToRoute(route, {
|
|
||||||
parameters: {
|
|
||||||
extensionId,
|
|
||||||
tabId,
|
|
||||||
},
|
|
||||||
withoutAffectingBackButton: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default navigateToExtensionPreferencesInjectable;
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import { computed } from "mobx";
|
|
||||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
|
||||||
|
|
||||||
const kubernetesPreferencesRouteInjectable = getInjectable({
|
|
||||||
id: "kubernetes-preferences-route",
|
|
||||||
|
|
||||||
instantiate: () => ({
|
|
||||||
path: "/preferences/kubernetes",
|
|
||||||
clusterFrame: false,
|
|
||||||
isEnabled: computed(() => true),
|
|
||||||
}),
|
|
||||||
|
|
||||||
injectionToken: frontEndRouteInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default kubernetesPreferencesRouteInjectable;
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import navigateToAppPreferencesInjectable from "./app/navigate-to-app-preferences.injectable";
|
|
||||||
|
|
||||||
const navigateToPreferencesInjectable = getInjectable({
|
|
||||||
id: "navigate-to-preferences",
|
|
||||||
|
|
||||||
instantiate: (di) => di.inject(navigateToAppPreferencesInjectable),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default navigateToPreferencesInjectable;
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import { computed } from "mobx";
|
|
||||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
|
||||||
|
|
||||||
const proxyPreferencesRouteInjectable = getInjectable({
|
|
||||||
id: "proxy-preferences-route",
|
|
||||||
|
|
||||||
instantiate: () => ({
|
|
||||||
path: "/preferences/proxy",
|
|
||||||
clusterFrame: false,
|
|
||||||
isEnabled: computed(() => true),
|
|
||||||
}),
|
|
||||||
|
|
||||||
injectionToken: frontEndRouteInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default proxyPreferencesRouteInjectable;
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import { computed } from "mobx";
|
|
||||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
|
||||||
|
|
||||||
const telemetryPreferencesRouteInjectable = getInjectable({
|
|
||||||
id: "telemetry-preferences-route",
|
|
||||||
|
|
||||||
instantiate: () => ({
|
|
||||||
path: "/preferences/telemetry",
|
|
||||||
clusterFrame: false,
|
|
||||||
isEnabled: computed(() => true),
|
|
||||||
}),
|
|
||||||
|
|
||||||
injectionToken: frontEndRouteInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default telemetryPreferencesRouteInjectable;
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import { computed } from "mobx";
|
|
||||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
|
||||||
|
|
||||||
const terminalPreferencesRouteInjectable = getInjectable({
|
|
||||||
id: "terminal-preferences-route",
|
|
||||||
|
|
||||||
instantiate: () => ({
|
|
||||||
path: "/preferences/terminal",
|
|
||||||
clusterFrame: false,
|
|
||||||
isEnabled: computed(() => true),
|
|
||||||
}),
|
|
||||||
|
|
||||||
injectionToken: frontEndRouteInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default terminalPreferencesRouteInjectable;
|
|
||||||
11
src/common/log-error.global-override-for-injectable.ts
Normal file
11
src/common/log-error.global-override-for-injectable.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getGlobalOverrideForFunction } from "./test-utils/get-global-override-for-function";
|
||||||
|
import logErrorInjectable from "./log-error.injectable";
|
||||||
|
|
||||||
|
// Note: this should remain as it is, and throw if called. Logging error is something
|
||||||
|
// that cannot happen without a unit test explicitly causing it. It cannot be allowed
|
||||||
|
// to happen without author of unit test knowing it.
|
||||||
|
export default getGlobalOverrideForFunction(logErrorInjectable);
|
||||||
25
src/common/test-utils/get-global-override-for-function.ts
Normal file
25
src/common/test-utils/get-global-override-for-function.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import type { Injectable } from "@ogre-tools/injectable";
|
||||||
|
import { getGlobalOverride } from "./get-global-override";
|
||||||
|
import { camelCase } from "lodash/fp";
|
||||||
|
|
||||||
|
export const getGlobalOverrideForFunction = (
|
||||||
|
injectable: Injectable<Function, any, any>,
|
||||||
|
) =>
|
||||||
|
getGlobalOverride(injectable, () => (...args: any[]) => {
|
||||||
|
console.warn(
|
||||||
|
`Tried to invoke a function "${injectable.id}" without override. The args were:`,
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`Tried to invoke a function "${
|
||||||
|
injectable.id
|
||||||
|
}" without override. Add eg. "di.override(${camelCase(
|
||||||
|
injectable.id,
|
||||||
|
)}Mock)" to the unit test interested in this.`,
|
||||||
|
);
|
||||||
|
});
|
||||||
@ -16,7 +16,7 @@ export const advanceFakeTime = (milliseconds: number) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useFakeTime = (dateTime: string) => {
|
export const useFakeTime = (dateTime = "2015-10-21T07:28:00Z") => {
|
||||||
usingFakeTime = true;
|
usingFakeTime = true;
|
||||||
|
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { UserStore } from "./user-store";
|
import { UserStore } from "./user-store";
|
||||||
import selectedUpdateChannelInjectable from "../application-update/selected-update-channel/selected-update-channel.injectable";
|
import selectedUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/selected-update-channel.injectable";
|
||||||
|
|
||||||
const userStoreInjectable = getInjectable({
|
const userStoreInjectable = getInjectable({
|
||||||
id: "user-store",
|
id: "user-store",
|
||||||
|
|||||||
@ -11,8 +11,10 @@ import { getOrInsertSet, toggle, toJS, object } from "../../renderer/utils";
|
|||||||
import { DESCRIPTORS } from "./preferences-helpers";
|
import { DESCRIPTORS } from "./preferences-helpers";
|
||||||
import type { UserPreferencesModel, StoreType } from "./preferences-helpers";
|
import type { UserPreferencesModel, StoreType } from "./preferences-helpers";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import type { SelectedUpdateChannel } from "../application-update/selected-update-channel/selected-update-channel.injectable";
|
|
||||||
import type { ReleaseChannel } from "../application-update/update-channels";
|
// TODO: Remove coupling with Feature
|
||||||
|
import type { SelectedUpdateChannel } from "../../features/application-update/common/selected-update-channel/selected-update-channel.injectable";
|
||||||
|
import type { ReleaseChannel } from "../../features/application-update/common/update-channels";
|
||||||
|
|
||||||
export interface UserStoreModel {
|
export interface UserStoreModel {
|
||||||
lastSeenAppVersion: string;
|
lastSeenAppVersion: string;
|
||||||
|
|||||||
53
src/common/utils/add-separator/add-separator.test.ts
Normal file
53
src/common/utils/add-separator/add-separator.test.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { addSeparator } from "./add-separator";
|
||||||
|
|
||||||
|
describe("add-separator", () => {
|
||||||
|
it("given multiple items, adds separators", () => {
|
||||||
|
const items = ["first", "second", "third"];
|
||||||
|
|
||||||
|
const actual = addSeparator((left, right) => `separator-between-${left}-and-${right}`, items);
|
||||||
|
|
||||||
|
expect(actual).toEqual([
|
||||||
|
"first",
|
||||||
|
"separator-between-first-and-second",
|
||||||
|
"second",
|
||||||
|
"separator-between-second-and-third",
|
||||||
|
"third",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given multiple items including falsy ones, adds separators", () => {
|
||||||
|
const items = [false, undefined, null, NaN];
|
||||||
|
|
||||||
|
const actual = addSeparator((left, right) => `separator-between-${left}-and-${right}`, items);
|
||||||
|
|
||||||
|
expect(actual).toEqual([
|
||||||
|
false,
|
||||||
|
"separator-between-false-and-undefined",
|
||||||
|
undefined,
|
||||||
|
"separator-between-undefined-and-null",
|
||||||
|
null,
|
||||||
|
"separator-between-null-and-NaN",
|
||||||
|
NaN,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given no items, does not add separator", () => {
|
||||||
|
const items: any[] = [];
|
||||||
|
|
||||||
|
const actual = addSeparator(() => "separator", items);
|
||||||
|
|
||||||
|
expect(actual).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given one item, does not add separator", () => {
|
||||||
|
const items = ["first"];
|
||||||
|
|
||||||
|
const actual = addSeparator(() => "separator", items);
|
||||||
|
|
||||||
|
expect(actual).toEqual(["first"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
26
src/common/utils/add-separator/add-separator.ts
Normal file
26
src/common/utils/add-separator/add-separator.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type GetSeparator<Item, Separator> = (left: Item, right: Item) => Separator;
|
||||||
|
|
||||||
|
export const addSeparator = <Item, Separator>(
|
||||||
|
getSeparator: GetSeparator<Item, Separator>,
|
||||||
|
items: Item[],
|
||||||
|
) => items.flatMap(toSeparatedTupleUsing(getSeparator));
|
||||||
|
|
||||||
|
const toSeparatedTupleUsing =
|
||||||
|
<Item, Separator>(getSeparator: GetSeparator<Item, Separator>) =>
|
||||||
|
(leftItem: Item, index: number, arr: Item[]) => {
|
||||||
|
const itemIsLast = arr.length === index + 1;
|
||||||
|
|
||||||
|
if (itemIsLast) {
|
||||||
|
return [leftItem];
|
||||||
|
}
|
||||||
|
|
||||||
|
const rightItem = arr[index + 1];
|
||||||
|
const separator = getSeparator(leftItem, rightItem);
|
||||||
|
|
||||||
|
return [leftItem, separator];
|
||||||
|
};
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// See: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions
|
||||||
|
export interface Discriminable<T extends string> { readonly kind: T }
|
||||||
|
|
||||||
|
// Note: this will fail at transpilation time, if all kinds are not instructed in switch/case.
|
||||||
|
// See: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking
|
||||||
|
export const checkThatAllDiscriminablesAreExhausted = <T extends never>(value: T) => {
|
||||||
|
const _exhaustiveCheck: never = value;
|
||||||
|
|
||||||
|
return new Error(
|
||||||
|
`Tried to exhaust discriminables, but no instructions were found for ${(_exhaustiveCheck as any).kind}`,
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
export interface Labelable {
|
||||||
|
readonly label: string;
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { sortBy } from "lodash/fp";
|
||||||
|
|
||||||
|
export interface Orderable {
|
||||||
|
readonly orderNumber: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MaybeOrderable = Orderable | object;
|
||||||
|
|
||||||
|
export const orderByOrderNumber = <T extends MaybeOrderable>(maybeOrderables: T[]) =>
|
||||||
|
sortBy(
|
||||||
|
(orderable) =>
|
||||||
|
"orderNumber" in orderable
|
||||||
|
? orderable.orderNumber
|
||||||
|
: Number.MAX_SAFE_INTEGER,
|
||||||
|
maybeOrderables,
|
||||||
|
);
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import { isBoolean } from "../../type-narrowing";
|
||||||
|
|
||||||
|
export interface Showable {
|
||||||
|
readonly isShown: IComputedValue<boolean> | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MaybeShowable = Showable | object;
|
||||||
|
|
||||||
|
export const isShown = (showable: MaybeShowable) => {
|
||||||
|
if (!("isShown" in showable)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showable.isShown === undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBoolean(showable.isShown)) {
|
||||||
|
return showable.isShown;
|
||||||
|
}
|
||||||
|
|
||||||
|
return showable.isShown.get();
|
||||||
|
};
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Composite } from "../get-composite/get-composite";
|
||||||
|
import { compositeHasDescendant } from "./composite-has-descendant";
|
||||||
|
import { getCompositeFor } from "../get-composite/get-composite";
|
||||||
|
|
||||||
|
describe("composite-has-descendant, given composite with children and grand children", () => {
|
||||||
|
let composite: Composite<{ id: string; parentId?: string }>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const items = [
|
||||||
|
{ id: "some-root-id", parentId: undefined },
|
||||||
|
{ id: "some-child-item", parentId: "some-root-id" },
|
||||||
|
|
||||||
|
{
|
||||||
|
id: "some-grand-child-item",
|
||||||
|
parentId: "some-child-item",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getComposite = getCompositeFor<{
|
||||||
|
id: string;
|
||||||
|
parentId?: string;
|
||||||
|
}>({
|
||||||
|
rootId: "some-root-id",
|
||||||
|
getId: (x) => x.id,
|
||||||
|
getParentId: (x) => x.parentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
composite = getComposite(items);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has a child as descendant", () => {
|
||||||
|
const actual = compositeHasDescendant<typeof composite["value"]>(
|
||||||
|
(referenceComposite) => referenceComposite.value.id === "some-child-item",
|
||||||
|
)(composite);
|
||||||
|
|
||||||
|
expect(actual).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has a grand child as descendant", () => {
|
||||||
|
const actual = compositeHasDescendant<typeof composite["value"]>(
|
||||||
|
(referenceComposite) =>
|
||||||
|
referenceComposite.value.id === "some-grand-child-item",
|
||||||
|
)(composite);
|
||||||
|
|
||||||
|
expect(actual).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not have an unrelated descendant", () => {
|
||||||
|
const actual = compositeHasDescendant<typeof composite["value"]>(
|
||||||
|
(referenceComposite) =>
|
||||||
|
referenceComposite.value.id === "some-unknown-item",
|
||||||
|
)(composite);
|
||||||
|
|
||||||
|
expect(actual).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import type { Composite } from "../get-composite/get-composite";
|
||||||
|
|
||||||
|
const compositeHasDescendant = <T>(
|
||||||
|
predicate: (referenceComposite: Composite<T>) => boolean,
|
||||||
|
) => {
|
||||||
|
const _compositeHasDescendant = (composite: Composite<T>): boolean =>
|
||||||
|
predicate(composite) ||
|
||||||
|
!!composite.children.find((childComposite) =>
|
||||||
|
_compositeHasDescendant(childComposite),
|
||||||
|
);
|
||||||
|
|
||||||
|
return _compositeHasDescendant;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { compositeHasDescendant };
|
||||||
@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import type { Composite } from "../get-composite/get-composite";
|
||||||
|
import { findComposite } from "./find-composite";
|
||||||
|
import { getCompositeFor } from "../get-composite/get-composite";
|
||||||
|
|
||||||
|
describe("find-composite", () => {
|
||||||
|
let composite: Composite<{ id: string; parentId?: string }>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const getComposite = getCompositeFor<{
|
||||||
|
id: string;
|
||||||
|
parentId?: string;
|
||||||
|
}>({
|
||||||
|
rootId: "some-root-id",
|
||||||
|
getId: (x) => x.id,
|
||||||
|
getParentId: (x) => x.parentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
composite = getComposite([
|
||||||
|
{ id: "some-root-id" },
|
||||||
|
{ id: "some-child-id", parentId: "some-root-id" },
|
||||||
|
{ id: "some-grandchild-id", parentId: "some-child-id" },
|
||||||
|
{ id: "some-other-grandchild-id", parentId: "some-child-id" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when finding root using path, does so", () => {
|
||||||
|
const actual = findComposite("some-root-id")(composite);
|
||||||
|
|
||||||
|
expect(actual.id).toBe("some-root-id");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when finding child using path, does so", () => {
|
||||||
|
const actual = findComposite("some-root-id", "some-child-id")(composite);
|
||||||
|
|
||||||
|
expect(actual.id).toBe("some-child-id");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when finding grandchild using path, does so", () => {
|
||||||
|
const actual = findComposite(
|
||||||
|
"some-root-id",
|
||||||
|
"some-child-id",
|
||||||
|
"some-grandchild-id",
|
||||||
|
)(composite);
|
||||||
|
|
||||||
|
expect(actual.id).toBe("some-grandchild-id");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when finding with non existing leaf-level path, throws", () => {
|
||||||
|
expect(() => {
|
||||||
|
findComposite(
|
||||||
|
"some-root-id",
|
||||||
|
"some-child-id",
|
||||||
|
"some-non-existing-grandchild-id",
|
||||||
|
)(composite);
|
||||||
|
}).toThrow(`Tried to find 'some-root-id -> some-child-id -> some-non-existing-grandchild-id' from a composite, but found nothing.
|
||||||
|
|
||||||
|
Node 'some-root-id -> some-child-id' had only following children:
|
||||||
|
some-grandchild-id
|
||||||
|
some-other-grandchild-id`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when finding with non-existing mid-level path, throws", () => {
|
||||||
|
expect(() => {
|
||||||
|
findComposite(
|
||||||
|
"some-root-id",
|
||||||
|
"some-non-existing-child-id",
|
||||||
|
"some-non-existing-grandchild-id",
|
||||||
|
)(composite);
|
||||||
|
}).toThrow(`Tried to find 'some-root-id -> some-non-existing-child-id -> some-non-existing-grandchild-id' from a composite, but found nothing.
|
||||||
|
|
||||||
|
Node 'some-root-id' had only following children:
|
||||||
|
some-child-id`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when finding with non-existing root-level path, throws", () => {
|
||||||
|
expect(() => {
|
||||||
|
findComposite(
|
||||||
|
"some-non-existing-root-id",
|
||||||
|
"some-non-existing-child-id",
|
||||||
|
"some-non-existing-grandchild-id",
|
||||||
|
)(composite);
|
||||||
|
}).toThrow(`Tried to find 'some-non-existing-root-id -> some-non-existing-child-id -> some-non-existing-grandchild-id' from a composite, but found nothing.
|
||||||
|
|
||||||
|
Node 'some-root-id' had only following children:
|
||||||
|
some-child-id`);
|
||||||
|
});
|
||||||
|
});
|
||||||
36
src/common/utils/composite/find-composite/find-composite.ts
Normal file
36
src/common/utils/composite/find-composite/find-composite.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import type { Composite } from "../get-composite/get-composite";
|
||||||
|
|
||||||
|
const _findComposite = <T>(currentLeftIds: string[], currentId: string, currentRightIds: string[], composite: Composite<T>): Composite<T> => {
|
||||||
|
const [nextId, ...nextRightIds] = currentRightIds;
|
||||||
|
const nextLeftIds = [...currentLeftIds, currentId];
|
||||||
|
|
||||||
|
if (currentRightIds.length === 0 && composite.id === currentId) {
|
||||||
|
return composite;
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundChildComposite = composite.children.find((child) => child.id === nextId);
|
||||||
|
|
||||||
|
if (foundChildComposite) {
|
||||||
|
return _findComposite(nextLeftIds, nextId, nextRightIds, foundChildComposite);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullPathString = [...currentLeftIds, currentId, ...currentRightIds].join(" -> ");
|
||||||
|
|
||||||
|
throw new Error(`Tried to find '${fullPathString}' from a composite, but found nothing.
|
||||||
|
|
||||||
|
Node '${[...currentLeftIds, composite.id].join(" -> ")}' had only following children:
|
||||||
|
${composite.children.map((child) => child.id).join("\n")}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findComposite =
|
||||||
|
(...path: string[]) =>
|
||||||
|
<T>(composite: Composite<T>): Composite<T> => {
|
||||||
|
const [currentId, ...rightIds] = path;
|
||||||
|
const leftIds: string[] = [];
|
||||||
|
|
||||||
|
return _findComposite(leftIds, currentId, rightIds, composite);
|
||||||
|
};
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getCompositeNormalization } from "./get-composite-normalization";
|
||||||
|
import { getCompositeFor } from "../get-composite/get-composite";
|
||||||
|
|
||||||
|
|
||||||
|
describe("get-composite-normalization", () => {
|
||||||
|
it("given a composite, flattens it to paths and composites", () => {
|
||||||
|
const someRootItem = {
|
||||||
|
id: "some-root-id",
|
||||||
|
parentId: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const someItem = {
|
||||||
|
id: "some-id",
|
||||||
|
parentId: "some-root-id",
|
||||||
|
};
|
||||||
|
|
||||||
|
const someNestedItem = {
|
||||||
|
id: "some-child-id",
|
||||||
|
parentId: "some-id",
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = [someRootItem, someItem, someNestedItem];
|
||||||
|
|
||||||
|
const getComposite = getCompositeFor<{
|
||||||
|
id: string;
|
||||||
|
parentId?: string;
|
||||||
|
orderNumber?: number;
|
||||||
|
}>({
|
||||||
|
rootId: "some-root-id",
|
||||||
|
getId: (x) => x.id,
|
||||||
|
getParentId: (x) => x.parentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const composite = getComposite(items);
|
||||||
|
|
||||||
|
const actual = getCompositeNormalization(composite);
|
||||||
|
|
||||||
|
expect(actual).toEqual([
|
||||||
|
[["some-root-id"], expect.objectContaining({ value: someRootItem })],
|
||||||
|
|
||||||
|
[["some-root-id", "some-id"], expect.objectContaining({ value: someItem })],
|
||||||
|
|
||||||
|
[
|
||||||
|
["some-root-id", "some-id", "some-child-id"],
|
||||||
|
expect.objectContaining({ value: someNestedItem }),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import type { Composite } from "../get-composite/get-composite";
|
||||||
|
|
||||||
|
export const getCompositeNormalization = <T>(composite: Composite<T>) => {
|
||||||
|
const _normalizeComposite = <T>(
|
||||||
|
composite: Composite<T>,
|
||||||
|
previousPath: string[] = [],
|
||||||
|
): (readonly [path: string[], composite: Composite<T>])[] => {
|
||||||
|
const currentPath = [...previousPath, composite.id];
|
||||||
|
|
||||||
|
const pathAndCompositeTuple = [currentPath, composite] as const;
|
||||||
|
|
||||||
|
return [
|
||||||
|
pathAndCompositeTuple,
|
||||||
|
|
||||||
|
...composite.children.flatMap((child) =>
|
||||||
|
_normalizeComposite(child, currentPath),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
return _normalizeComposite(composite);
|
||||||
|
};
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getCompositePaths } from "./get-composite-paths";
|
||||||
|
import { sortBy } from "lodash/fp";
|
||||||
|
import { getCompositeFor } from "../get-composite/get-composite";
|
||||||
|
|
||||||
|
describe("get-composite-paths", () => {
|
||||||
|
it("given composite with transformed children, returns paths of transformed children in hierarchical order", () => {
|
||||||
|
const someRootItem = {
|
||||||
|
id: "some-root-id",
|
||||||
|
};
|
||||||
|
|
||||||
|
const someChildItem1 = {
|
||||||
|
id: "some-child-id-1",
|
||||||
|
parentId: "some-root-id",
|
||||||
|
orderNumber: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const someChildItem2 = {
|
||||||
|
id: "some-child-id-2",
|
||||||
|
parentId: "some-root-id",
|
||||||
|
orderNumber: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const someGrandchildItem1 = {
|
||||||
|
id: "some-grandchild-id-1",
|
||||||
|
parentId: "some-child-id-1",
|
||||||
|
orderNumber: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const someGrandchildItem2 = {
|
||||||
|
id: "some-grandchild-id-2",
|
||||||
|
parentId: "some-child-id-1",
|
||||||
|
orderNumber: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
someRootItem,
|
||||||
|
// Note: not in order yet.
|
||||||
|
someChildItem2,
|
||||||
|
someChildItem1,
|
||||||
|
someGrandchildItem2,
|
||||||
|
someGrandchildItem1,
|
||||||
|
];
|
||||||
|
|
||||||
|
const getComposite = getCompositeFor<{
|
||||||
|
id: string;
|
||||||
|
parentId?: string;
|
||||||
|
orderNumber?: number;
|
||||||
|
}>({
|
||||||
|
rootId: "some-root-id",
|
||||||
|
getId: (x) => x.id,
|
||||||
|
getParentId: (x) => x.parentId,
|
||||||
|
transformChildren: children => sortBy(child => child.orderNumber, children),
|
||||||
|
});
|
||||||
|
|
||||||
|
const composite = getComposite(items);
|
||||||
|
|
||||||
|
const actual = getCompositePaths(composite);
|
||||||
|
|
||||||
|
expect(actual).toEqual([
|
||||||
|
["some-root-id"],
|
||||||
|
["some-root-id", "some-child-id-1"],
|
||||||
|
["some-root-id", "some-child-id-1", "some-grandchild-id-1"],
|
||||||
|
["some-root-id", "some-child-id-1", "some-grandchild-id-2"],
|
||||||
|
["some-root-id", "some-child-id-2"],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { pipeline } from "@ogre-tools/fp";
|
||||||
|
import { map } from "lodash/fp";
|
||||||
|
import type { Composite } from "../get-composite/get-composite";
|
||||||
|
import { getCompositeNormalization } from "../get-composite-normalization/get-composite-normalization";
|
||||||
|
|
||||||
|
export const getCompositePaths = (
|
||||||
|
composite: Composite<unknown>,
|
||||||
|
): string[][] => pipeline(composite, getCompositeNormalization, map(([path]) => path));
|
||||||
363
src/common/utils/composite/get-composite/get-composite.test.ts
Normal file
363
src/common/utils/composite/get-composite/get-composite.test.ts
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Composite } from "./get-composite";
|
||||||
|
import { getCompositePaths } from "../get-composite-paths/get-composite-paths";
|
||||||
|
import { sortBy } from "lodash/fp";
|
||||||
|
import { getCompositeFor } from "./get-composite";
|
||||||
|
|
||||||
|
interface SomeItem {
|
||||||
|
id: string;
|
||||||
|
parentId?: string;
|
||||||
|
orderNumber?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("get-composite", () => {
|
||||||
|
it("given items and an explicit root id, creates a composite", () => {
|
||||||
|
const someRootItem = {
|
||||||
|
id: "some-root-id",
|
||||||
|
someProperty: "some-root-content",
|
||||||
|
};
|
||||||
|
|
||||||
|
const someIrrelevantRootItem = {
|
||||||
|
id: "some-irrelevant-root-id",
|
||||||
|
someProperty: "some-other-root-content",
|
||||||
|
};
|
||||||
|
|
||||||
|
const someItem = {
|
||||||
|
id: "some-id",
|
||||||
|
parentId: "some-root-id",
|
||||||
|
someProperty: "some-content",
|
||||||
|
};
|
||||||
|
|
||||||
|
const someNestedItem = {
|
||||||
|
id: "some-nested-id",
|
||||||
|
parentId: "some-id",
|
||||||
|
someProperty: "some-nested-content",
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = [someRootItem, someIrrelevantRootItem, someItem, someNestedItem];
|
||||||
|
|
||||||
|
const getComposite = getCompositeFor<SomeItem>({
|
||||||
|
rootId: "some-root-id",
|
||||||
|
getId: (x) => x.id,
|
||||||
|
getParentId: (x) => x.parentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const composite = getComposite(items);
|
||||||
|
|
||||||
|
expect(composite).toEqual({
|
||||||
|
id: "some-root-id",
|
||||||
|
value: someRootItem,
|
||||||
|
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "some-id",
|
||||||
|
parentId: "some-root-id",
|
||||||
|
value: someItem,
|
||||||
|
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "some-nested-id",
|
||||||
|
parentId: "some-id",
|
||||||
|
value: someNestedItem,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given items and implicit root, creates a composite", () => {
|
||||||
|
const someRootItem = {
|
||||||
|
id: "some-root-id",
|
||||||
|
someProperty: "some-root-content",
|
||||||
|
// Notice: no "parentId" makes this the implicit root.
|
||||||
|
parentId: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const someItem = {
|
||||||
|
id: "some-id",
|
||||||
|
parentId: "some-root-id",
|
||||||
|
someProperty: "some-content",
|
||||||
|
};
|
||||||
|
|
||||||
|
const someNestedItem = {
|
||||||
|
id: "some-nested-id",
|
||||||
|
parentId: "some-id",
|
||||||
|
someProperty: "some-nested-content",
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = [someRootItem, someItem, someNestedItem];
|
||||||
|
|
||||||
|
const getComposite = getCompositeFor<SomeItem>({
|
||||||
|
// Notice: no root id
|
||||||
|
// rootId: "some-root-id",
|
||||||
|
getId: (x) => x.id,
|
||||||
|
getParentId: (x) => x.parentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const composite = getComposite(items);
|
||||||
|
|
||||||
|
expect(composite).toEqual({
|
||||||
|
id: "some-root-id",
|
||||||
|
value: someRootItem,
|
||||||
|
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "some-id",
|
||||||
|
parentId: "some-root-id",
|
||||||
|
value: someItem,
|
||||||
|
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "some-nested-id",
|
||||||
|
parentId: "some-id",
|
||||||
|
value: someNestedItem,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given items and an unspecified root id and multiple items without parent as root, throws", () => {
|
||||||
|
const someRootItem = {
|
||||||
|
id: "some-root-id",
|
||||||
|
// Notice: no "parentId" makes this a root.
|
||||||
|
parentId: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const someOtherRootItem = {
|
||||||
|
id: "some-other-root-id",
|
||||||
|
// Notice: no "parentId" makes also this a root.
|
||||||
|
parentId: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = [someRootItem, someOtherRootItem];
|
||||||
|
|
||||||
|
const getComposite = getCompositeFor<SomeItem>({
|
||||||
|
getId: (x) => x.id,
|
||||||
|
getParentId: (x) => x.parentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
getComposite(items);
|
||||||
|
}).toThrow(
|
||||||
|
'Tried to get a composite, but multiple roots where encountered: "some-root-id", "some-other-root-id"',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given non-unique ids, throws", () => {
|
||||||
|
const someItem = {
|
||||||
|
id: "some-id",
|
||||||
|
parentId: "irrelevant",
|
||||||
|
};
|
||||||
|
|
||||||
|
const someOtherItem = {
|
||||||
|
id: "some-id",
|
||||||
|
parentId: "irrelevant",
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = [someItem, someOtherItem];
|
||||||
|
|
||||||
|
const getComposite = getCompositeFor<SomeItem>({
|
||||||
|
getId: (x) => x.id,
|
||||||
|
getParentId: (x) => x.parentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
getComposite(items);
|
||||||
|
}).toThrow(
|
||||||
|
'Tried to get a composite but encountered non-unique ids: "some-id"',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given items with missing parent ids, when creating composite without handling for unknown parents, throws", () => {
|
||||||
|
const someItem = {
|
||||||
|
id: "some-id",
|
||||||
|
parentId: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const someItemWithMissingParentId = {
|
||||||
|
id: "some-other-id",
|
||||||
|
parentId: "some-missing-id",
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = [someItem, someItemWithMissingParentId];
|
||||||
|
|
||||||
|
const getComposite = getCompositeFor<SomeItem>({
|
||||||
|
getId: (x) => x.id,
|
||||||
|
getParentId: (x) => x.parentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
getComposite(items);
|
||||||
|
}).toThrow(
|
||||||
|
`Tried to get a composite but encountered missing parent ids: "some-missing-id".
|
||||||
|
|
||||||
|
Available parent ids are:
|
||||||
|
"some-id",
|
||||||
|
"some-other-id"`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given items with missing parents, when creating composite with handling for missing parents", () => {
|
||||||
|
let composite: Composite<any>;
|
||||||
|
let handleMissingParentIdMock: jest.Mock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const someItem = {
|
||||||
|
id: "some-root-id",
|
||||||
|
};
|
||||||
|
|
||||||
|
const someItemWithMissingParentId = {
|
||||||
|
id: "some-orphan-id",
|
||||||
|
// Note: the item corresponding to this id does not exist,
|
||||||
|
// making this item have a "missing parent".
|
||||||
|
parentId: "some-missing-id",
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = [someItem, someItemWithMissingParentId];
|
||||||
|
|
||||||
|
handleMissingParentIdMock = jest.fn();
|
||||||
|
|
||||||
|
const getComposite = getCompositeFor<SomeItem>({
|
||||||
|
getId: (x) => x.id,
|
||||||
|
getParentId: (x) => x.parentId,
|
||||||
|
handleMissingParentIds: handleMissingParentIdMock,
|
||||||
|
});
|
||||||
|
|
||||||
|
composite = getComposite(items);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("creates composite without the orphan item, and without throwing", () => {
|
||||||
|
const paths = getCompositePaths(composite);
|
||||||
|
|
||||||
|
expect(paths).toEqual([["some-root-id"]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles the missing parent ids", () => {
|
||||||
|
expect(handleMissingParentIdMock).toHaveBeenCalledWith({
|
||||||
|
missingParentIds: ["some-missing-id"],
|
||||||
|
availableParentIds: ["some-root-id", "some-orphan-id"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given items with same id and parent id, throws", () => {
|
||||||
|
const someItem = {
|
||||||
|
id: "some-id",
|
||||||
|
parentId: "some-id",
|
||||||
|
};
|
||||||
|
|
||||||
|
const someRoot = {
|
||||||
|
id: "root",
|
||||||
|
parentId: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = [someItem, someRoot];
|
||||||
|
|
||||||
|
const getComposite = getCompositeFor<SomeItem>({
|
||||||
|
getId: (x) => x.id,
|
||||||
|
getParentId: (x) => x.parentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
getComposite(items);
|
||||||
|
}).toThrow(
|
||||||
|
'Tried to get a composite, but found items with self as parent: "some-id"',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given undefined ids, throws", () => {
|
||||||
|
const root = {
|
||||||
|
parentId: undefined,
|
||||||
|
id: "some-root",
|
||||||
|
};
|
||||||
|
|
||||||
|
const someItem = {
|
||||||
|
parentId: "some-root",
|
||||||
|
id: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const someOtherItem = {
|
||||||
|
parentId: "some-root",
|
||||||
|
id: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = [root, someItem, someOtherItem];
|
||||||
|
|
||||||
|
const getComposite = getCompositeFor<any>({
|
||||||
|
getId: (x) => x.id,
|
||||||
|
getParentId: (x) => x.parentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
getComposite(items);
|
||||||
|
}).toThrow("Tried to get a composite but encountered 2 undefined ids");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given transformed children, creates a composite with transformed children", () => {
|
||||||
|
const someRootItem = {
|
||||||
|
id: "some-root-id",
|
||||||
|
orderNumber: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const someItem1 = {
|
||||||
|
id: "some-id-1",
|
||||||
|
parentId: "some-root-id",
|
||||||
|
orderNumber: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const someItem2 = {
|
||||||
|
id: "some-id-2",
|
||||||
|
parentId: "some-root-id",
|
||||||
|
orderNumber: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const someChildItem1 = {
|
||||||
|
id: "some-child-id-1",
|
||||||
|
parentId: "some-id-1",
|
||||||
|
orderNumber: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const someChildItem2 = {
|
||||||
|
id: "some-child-id-2",
|
||||||
|
parentId: "some-id-1",
|
||||||
|
orderNumber: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
someRootItem,
|
||||||
|
// Note: not in order yet.
|
||||||
|
someItem2,
|
||||||
|
someItem1,
|
||||||
|
someChildItem2,
|
||||||
|
someChildItem1,
|
||||||
|
];
|
||||||
|
|
||||||
|
const getComposite = getCompositeFor<SomeItem>({
|
||||||
|
getId: (x) => x.id,
|
||||||
|
getParentId: (x) => x.parentId,
|
||||||
|
transformChildren: (things) =>
|
||||||
|
sortBy((thing) => thing.orderNumber, things),
|
||||||
|
});
|
||||||
|
|
||||||
|
const composite = getComposite(items);
|
||||||
|
|
||||||
|
const orderedPaths = getCompositePaths(composite);
|
||||||
|
|
||||||
|
expect(orderedPaths).toEqual([
|
||||||
|
["some-root-id"],
|
||||||
|
["some-root-id", "some-id-1"],
|
||||||
|
["some-root-id", "some-id-1", "some-child-id-1"],
|
||||||
|
["some-root-id", "some-id-1", "some-child-id-2"],
|
||||||
|
["some-root-id", "some-id-2"],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
147
src/common/utils/composite/get-composite/get-composite.ts
Normal file
147
src/common/utils/composite/get-composite/get-composite.ts
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { pipeline } from "@ogre-tools/fp";
|
||||||
|
import {
|
||||||
|
countBy,
|
||||||
|
filter,
|
||||||
|
toPairs,
|
||||||
|
nth,
|
||||||
|
map,
|
||||||
|
uniq,
|
||||||
|
without,
|
||||||
|
compact,
|
||||||
|
identity,
|
||||||
|
} from "lodash/fp";
|
||||||
|
|
||||||
|
export interface Composite<T> {
|
||||||
|
id: string;
|
||||||
|
parentId: string | undefined;
|
||||||
|
value: T;
|
||||||
|
children: Composite<T>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Configuration<T> {
|
||||||
|
rootId?: string;
|
||||||
|
getId: (thing: T) => string;
|
||||||
|
getParentId: (thing: T) => string | undefined;
|
||||||
|
transformChildren?: (things: T[]) => T[];
|
||||||
|
handleMissingParentIds?: (parentIdsForHandling: ParentIdsForHandling) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCompositeFor = <T>({
|
||||||
|
rootId = undefined,
|
||||||
|
getId,
|
||||||
|
getParentId,
|
||||||
|
transformChildren = identity,
|
||||||
|
handleMissingParentIds = throwMissingParentIds,
|
||||||
|
}: Configuration<T>) => (source: T[]) => {
|
||||||
|
const undefinedIds = pipeline(
|
||||||
|
source,
|
||||||
|
filter((x) => getId(x) === undefined),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (undefinedIds.length) {
|
||||||
|
throw new Error(
|
||||||
|
`Tried to get a composite but encountered ${undefinedIds.length} undefined ids`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const selfReferencingIds = pipeline(
|
||||||
|
source,
|
||||||
|
filter((x) => getId(x) === getParentId(x)),
|
||||||
|
map(getId),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selfReferencingIds.length) {
|
||||||
|
throw new Error(
|
||||||
|
`Tried to get a composite, but found items with self as parent: "${selfReferencingIds.join(
|
||||||
|
'", ',
|
||||||
|
)}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const duplicateIds = pipeline(
|
||||||
|
source,
|
||||||
|
countBy(getId),
|
||||||
|
toPairs,
|
||||||
|
filter(([, count]) => count > 1),
|
||||||
|
map(nth(0)),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (duplicateIds.length) {
|
||||||
|
throw new Error(
|
||||||
|
`Tried to get a composite but encountered non-unique ids: "${duplicateIds
|
||||||
|
.map((x) => String(x))
|
||||||
|
.join('", "')}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const allIds = pipeline(source, map(getId));
|
||||||
|
|
||||||
|
const allParentIds = pipeline(source, map(getParentId), uniq, compact);
|
||||||
|
|
||||||
|
const missingParentIds = without(allIds, allParentIds);
|
||||||
|
|
||||||
|
if (missingParentIds.length) {
|
||||||
|
handleMissingParentIds({ missingParentIds, availableParentIds: allIds });
|
||||||
|
}
|
||||||
|
|
||||||
|
const toComposite = (thing: T): Composite<T> => {
|
||||||
|
const thingId = getId(thing);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: thingId,
|
||||||
|
parentId: getParentId(thing),
|
||||||
|
value: thing,
|
||||||
|
|
||||||
|
children: pipeline(
|
||||||
|
source,
|
||||||
|
|
||||||
|
filter((childThing) => {
|
||||||
|
const parentId = getParentId(childThing);
|
||||||
|
|
||||||
|
return parentId === thingId;
|
||||||
|
}),
|
||||||
|
|
||||||
|
transformChildren,
|
||||||
|
|
||||||
|
map(toComposite),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const isRootId = rootId
|
||||||
|
? (thing: T) => getId(thing) === rootId
|
||||||
|
: (thing: T) => getParentId(thing) === undefined;
|
||||||
|
|
||||||
|
const roots = source.filter(isRootId);
|
||||||
|
|
||||||
|
if (roots.length > 1) {
|
||||||
|
throw new Error(
|
||||||
|
`Tried to get a composite, but multiple roots where encountered: "${roots
|
||||||
|
.map(getId)
|
||||||
|
.join('", "')}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toComposite(roots[0]);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ParentIdsForHandling {
|
||||||
|
missingParentIds: string[];
|
||||||
|
availableParentIds: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const throwMissingParentIds = ({
|
||||||
|
missingParentIds,
|
||||||
|
availableParentIds,
|
||||||
|
}: ParentIdsForHandling) => {
|
||||||
|
throw new Error(
|
||||||
|
`Tried to get a composite but encountered missing parent ids: "${missingParentIds.join(
|
||||||
|
'", "',
|
||||||
|
)}".\n\nAvailable parent ids are:\n"${availableParentIds.join('",\n"')}"`,
|
||||||
|
);
|
||||||
|
};
|
||||||
16
src/common/utils/composite/interfaces.ts
Normal file
16
src/common/utils/composite/interfaces.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ParentOfChildComposite<Id extends string = string> {
|
||||||
|
id: Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChildOfParentComposite<ParentId extends string = string> {
|
||||||
|
parentId: ParentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RootComposite<Id extends string = string> =
|
||||||
|
& { parentId: undefined }
|
||||||
|
& ParentOfChildComposite<Id>;
|
||||||
34
src/common/utils/find-exactly-one/find-exactly-one.test.ts
Normal file
34
src/common/utils/find-exactly-one/find-exactly-one.test.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { findExactlyOne } from "./find-exactly-one";
|
||||||
|
|
||||||
|
describe("find-exactly-one", () => {
|
||||||
|
it("when predicate matches to single item, returns the item", () => {
|
||||||
|
const actual = findExactlyOne((item) => item === "some-item")([
|
||||||
|
"some-item",
|
||||||
|
"some-other-item",
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(actual).toBe("some-item");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when predicate matches to many items, throws", () => {
|
||||||
|
expect(() => {
|
||||||
|
findExactlyOne((item) => item === "some-item")([
|
||||||
|
"some-item",
|
||||||
|
"some-item",
|
||||||
|
]);
|
||||||
|
}).toThrow("Tried to find exactly one, but found many");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when predicate does not match, throws", () => {
|
||||||
|
expect(() => {
|
||||||
|
findExactlyOne((item) => item === "some-item")([
|
||||||
|
"some-other-item",
|
||||||
|
]);
|
||||||
|
}).toThrow("Tried to find exactly one, but didn't find any");
|
||||||
|
});
|
||||||
|
});
|
||||||
21
src/common/utils/find-exactly-one/find-exactly-one.ts
Normal file
21
src/common/utils/find-exactly-one/find-exactly-one.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
export const findExactlyOne = <T>(predicate: (item: T) => boolean) => (collection: T[]): T => {
|
||||||
|
const itemsFound = collection.filter(predicate);
|
||||||
|
|
||||||
|
if (!itemsFound.length) {
|
||||||
|
throw new Error(
|
||||||
|
"Tried to find exactly one, but didn't find any",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemsFound.length > 1) {
|
||||||
|
throw new Error(
|
||||||
|
"Tried to find exactly one, but found many",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemsFound[0];
|
||||||
|
};
|
||||||
@ -35,6 +35,15 @@ export function hasTypedProperty<S extends object, K extends PropertyKey, V>(val
|
|||||||
return hasOwnProperty(val, key) && isValid(val[key]);
|
return hasOwnProperty(val, key) && isValid(val[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Narrows `val` to include the property `key` with type string
|
||||||
|
* @param val the value that we are trying to type narrow
|
||||||
|
* @param key The key to test if it is present on the object (must be a literal for tsc to do any meaningful typing)
|
||||||
|
*/
|
||||||
|
export function hasStringProperty<S extends object, K extends PropertyKey>(val: S, key: K): val is (S & { [key in K]: string }) {
|
||||||
|
return hasOwnProperty(val, key) && isString(val[key]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Narrows `val` to include the property `key` with type `V | undefined` or doesn't contain it
|
* Narrows `val` to include the property `key` with type `V | undefined` or doesn't contain it
|
||||||
* @param val the value that we are trying to type narrow
|
* @param val the value that we are trying to type narrow
|
||||||
|
|||||||
@ -4,28 +4,25 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting";
|
||||||
import loggerInjectable from "../../logger.injectable";
|
|
||||||
import type { Logger } from "../../logger";
|
|
||||||
import withErrorLoggingInjectable from "./with-error-logging.injectable";
|
import withErrorLoggingInjectable from "./with-error-logging.injectable";
|
||||||
import { pipeline } from "@ogre-tools/fp";
|
import { pipeline } from "@ogre-tools/fp";
|
||||||
import type { AsyncFnMock } from "@async-fn/jest";
|
import type { AsyncFnMock } from "@async-fn/jest";
|
||||||
import asyncFn from "@async-fn/jest";
|
import asyncFn from "@async-fn/jest";
|
||||||
import { getPromiseStatus } from "../../test-utils/get-promise-status";
|
import { getPromiseStatus } from "../../test-utils/get-promise-status";
|
||||||
|
import logErrorInjectable from "../../log-error.injectable";
|
||||||
|
|
||||||
describe("with-error-logging", () => {
|
describe("with-error-logging", () => {
|
||||||
describe("given decorated sync function", () => {
|
describe("given decorated sync function", () => {
|
||||||
let loggerStub: Logger;
|
|
||||||
let toBeDecorated: jest.Mock<number | undefined, [string, string]>;
|
let toBeDecorated: jest.Mock<number | undefined, [string, string]>;
|
||||||
let decorated: (a: string, b: string) => number | undefined;
|
let decorated: (a: string, b: string) => number | undefined;
|
||||||
|
let logErrorMock: jest.Mock;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
loggerStub = {
|
logErrorMock = jest.fn();
|
||||||
error: jest.fn(),
|
|
||||||
} as unknown as Logger;
|
|
||||||
|
|
||||||
di.override(loggerInjectable, () => loggerStub);
|
di.override(logErrorInjectable, () => logErrorMock);
|
||||||
|
|
||||||
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
|
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
|
||||||
|
|
||||||
@ -52,7 +49,7 @@ describe("with-error-logging", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does not log error", () => {
|
it("does not log error", () => {
|
||||||
expect(loggerStub.error).not.toHaveBeenCalled();
|
expect(logErrorMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns the value", () => {
|
it("returns the value", () => {
|
||||||
@ -75,7 +72,7 @@ describe("with-error-logging", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does not log error", () => {
|
it("does not log error", () => {
|
||||||
expect(loggerStub.error).not.toHaveBeenCalled();
|
expect(logErrorMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns nothing", () => {
|
it("returns nothing", () => {
|
||||||
@ -104,7 +101,7 @@ describe("with-error-logging", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("logs the error", () => {
|
it("logs the error", () => {
|
||||||
expect(loggerStub.error).toHaveBeenCalledWith("some-error-message-for-some-error", error);
|
expect(logErrorMock).toHaveBeenCalledWith("some-error-message-for-some-error", error);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws", () => {
|
it("throws", () => {
|
||||||
@ -114,18 +111,16 @@ describe("with-error-logging", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("given decorated async function", () => {
|
describe("given decorated async function", () => {
|
||||||
let loggerStub: Logger;
|
|
||||||
let decorated: (a: string, b: string) => Promise<number | undefined>;
|
let decorated: (a: string, b: string) => Promise<number | undefined>;
|
||||||
let toBeDecorated: AsyncFnMock<typeof decorated>;
|
let toBeDecorated: AsyncFnMock<typeof decorated>;
|
||||||
|
let logErrorMock: jest.Mock;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
loggerStub = {
|
logErrorMock = jest.fn();
|
||||||
error: jest.fn(),
|
|
||||||
} as unknown as Logger;
|
|
||||||
|
|
||||||
di.override(loggerInjectable, () => loggerStub);
|
di.override(logErrorInjectable, () => logErrorMock);
|
||||||
|
|
||||||
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
|
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
|
||||||
|
|
||||||
@ -153,7 +148,7 @@ describe("with-error-logging", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does not log error yet", () => {
|
it("does not log error yet", () => {
|
||||||
expect(loggerStub.error).not.toHaveBeenCalled();
|
expect(logErrorMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not resolve yet", async () => {
|
it("does not resolve yet", async () => {
|
||||||
@ -176,7 +171,7 @@ describe("with-error-logging", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(loggerStub.error).toHaveBeenCalledWith("some-error-message-for-some-error", error);
|
expect(logErrorMock).toHaveBeenCalledWith("some-error-message-for-some-error", error);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects", () => {
|
it("rejects", () => {
|
||||||
@ -198,7 +193,7 @@ describe("with-error-logging", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("logs the rejection", () => {
|
it("logs the rejection", () => {
|
||||||
expect(loggerStub.error).toHaveBeenCalledWith(
|
expect(logErrorMock).toHaveBeenCalledWith(
|
||||||
"some-error-message-for-some-rejection",
|
"some-error-message-for-some-rejection",
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
@ -215,7 +210,7 @@ describe("with-error-logging", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does not log error", () => {
|
it("does not log error", () => {
|
||||||
expect(loggerStub.error).not.toHaveBeenCalled();
|
expect(logErrorMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("resolves with the value", async () => {
|
it("resolves with the value", async () => {
|
||||||
@ -231,7 +226,7 @@ describe("with-error-logging", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does not log error", () => {
|
it("does not log error", () => {
|
||||||
expect(loggerStub.error).not.toHaveBeenCalled();
|
expect(logErrorMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("resolves without value", async () => {
|
it("resolves without value", async () => {
|
||||||
|
|||||||
@ -5,21 +5,20 @@
|
|||||||
import type { AsyncFnMock } from "@async-fn/jest";
|
import type { AsyncFnMock } from "@async-fn/jest";
|
||||||
import asyncFn from "@async-fn/jest";
|
import asyncFn from "@async-fn/jest";
|
||||||
import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting";
|
||||||
import loggerInjectable from "../../logger.injectable";
|
|
||||||
import type { Logger } from "../../logger";
|
|
||||||
import withOrphanPromiseInjectable from "./with-orphan-promise.injectable";
|
import withOrphanPromiseInjectable from "./with-orphan-promise.injectable";
|
||||||
|
import logErrorInjectable from "../../log-error.injectable";
|
||||||
|
|
||||||
describe("with orphan promise, when called", () => {
|
describe("with orphan promise, when called", () => {
|
||||||
let toBeDecorated: AsyncFnMock<(arg1: string, arg2: string) => Promise<string>>;
|
let toBeDecorated: AsyncFnMock<(arg1: string, arg2: string) => Promise<string>>;
|
||||||
let actual: void;
|
let actual: void;
|
||||||
let loggerStub: Logger;
|
let logErrorMock: jest.Mock;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
loggerStub = { error: jest.fn() } as unknown as Logger;
|
logErrorMock = jest.fn();
|
||||||
|
|
||||||
di.override(loggerInjectable, () => loggerStub);
|
di.override(logErrorInjectable, () => logErrorMock);
|
||||||
|
|
||||||
const withOrphanPromise = di.inject(withOrphanPromiseInjectable);
|
const withOrphanPromise = di.inject(withOrphanPromiseInjectable);
|
||||||
|
|
||||||
@ -49,7 +48,7 @@ describe("with orphan promise, when called", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("logs the rejection", () => {
|
it("logs the rejection", () => {
|
||||||
expect(loggerStub.error).toHaveBeenCalledWith("Orphan promise rejection encountered", "some-error");
|
expect(logErrorMock).toHaveBeenCalledWith("Orphan promise rejection encountered", "some-error");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("nothing else happens", () => {
|
it("nothing else happens", () => {
|
||||||
|
|||||||
@ -4,9 +4,12 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
// Todo: OCP by creating distinct injectables for platforms.
|
||||||
|
export const allPlatforms = ["win32", "darwin", "linux"] as const;
|
||||||
|
|
||||||
const platformInjectable = getInjectable({
|
const platformInjectable = getInjectable({
|
||||||
id: "platform",
|
id: "platform",
|
||||||
instantiate: () => process.platform,
|
instantiate: () => process.platform as typeof allPlatforms[number],
|
||||||
causesSideEffects: true,
|
causesSideEffects: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { ReleaseChannel } from "../application-update/update-channels";
|
|
||||||
import { createInitializableState } from "../initializable-state/create";
|
import { createInitializableState } from "../initializable-state/create";
|
||||||
import buildSemanticVersionInjectable from "./build-semantic-version.injectable";
|
import buildSemanticVersionInjectable from "./build-semantic-version.injectable";
|
||||||
|
import type { ReleaseChannel } from "../../features/application-update/common/update-channels";
|
||||||
|
|
||||||
const releaseChannelInjectable = createInitializableState({
|
const releaseChannelInjectable = createInitializableState({
|
||||||
id: "release-channel",
|
id: "release-channel",
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
export type { StatusBarRegistration } from "../../renderer/components/status-bar/status-bar-registration";
|
export type { StatusBarRegistration } from "../../renderer/components/status-bar/status-bar-registration";
|
||||||
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../../renderer/components/kube-object-menu/kube-object-menu-registration";
|
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../../renderer/components/kube-object-menu/kube-object-menu-registration";
|
||||||
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../../renderer/components/+preferences/app-preferences/app-preference-registration";
|
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../../features/preferences/renderer/compliance-for-legacy-extension-api/app-preference-registration";
|
||||||
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../../renderer/components/kube-object-details/kube-object-detail-registration";
|
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../../renderer/components/kube-object-details/kube-object-detail-registration";
|
||||||
export type { KubeObjectStatusRegistration } from "../../renderer/components/kube-object-status-icon/kube-object-status-registration";
|
export type { KubeObjectStatusRegistration } from "../../renderer/components/kube-object-status-icon/kube-object-status-registration";
|
||||||
export type { PageRegistration, RegisteredPage, PageParams, PageComponentProps, PageComponents, PageTarget } from "../registries/page-registry";
|
export type { PageRegistration, RegisteredPage, PageParams, PageComponentProps, PageComponents, PageTarget } from "../registries/page-registry";
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
import { LensExtension, lensExtensionDependencies } from "./lens-extension";
|
import { LensExtension, lensExtensionDependencies } from "./lens-extension";
|
||||||
import type { CatalogEntity } from "../common/catalog";
|
import type { CatalogEntity } from "../common/catalog";
|
||||||
import type { IObservableArray } from "mobx";
|
import type { IObservableArray } from "mobx";
|
||||||
import type { MenuRegistration } from "../main/menu/menu-registration";
|
import type { MenuRegistration } from "../features/application-menu/main/menu-registration";
|
||||||
import type { TrayMenuRegistration } from "../main/tray/tray-menu-registration";
|
import type { TrayMenuRegistration } from "../main/tray/tray-menu-registration";
|
||||||
import type { ShellEnvModifier } from "../main/shell-session/shell-env-modifier/shell-env-modifier-registration";
|
import type { ShellEnvModifier } from "../main/shell-session/shell-env-modifier/shell-env-modifier-registration";
|
||||||
import type { LensMainExtensionDependencies } from "./lens-extension-set-dependencies";
|
import type { LensMainExtensionDependencies } from "./lens-extension-set-dependencies";
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import type { KubernetesCluster } from "../common/catalog-entities";
|
|||||||
import type { WelcomeMenuRegistration } from "../renderer/components/+welcome/welcome-menu-items/welcome-menu-registration";
|
import type { WelcomeMenuRegistration } from "../renderer/components/+welcome/welcome-menu-items/welcome-menu-registration";
|
||||||
import type { WelcomeBannerRegistration } from "../renderer/components/+welcome/welcome-banner-items/welcome-banner-registration";
|
import type { WelcomeBannerRegistration } from "../renderer/components/+welcome/welcome-banner-items/welcome-banner-registration";
|
||||||
import type { CommandRegistration } from "../renderer/components/command-palette/registered-commands/commands";
|
import type { CommandRegistration } from "../renderer/components/command-palette/registered-commands/commands";
|
||||||
import type { AppPreferenceRegistration } from "../renderer/components/+preferences/app-preferences/app-preference-registration";
|
import type { AppPreferenceRegistration } from "../features/preferences/renderer/compliance-for-legacy-extension-api/app-preference-registration";
|
||||||
import type { AdditionalCategoryColumnRegistration } from "../renderer/components/+catalog/custom-category-columns";
|
import type { AdditionalCategoryColumnRegistration } from "../renderer/components/+catalog/custom-category-columns";
|
||||||
import type { CustomCategoryViewRegistration } from "../renderer/components/+catalog/custom-views";
|
import type { CustomCategoryViewRegistration } from "../renderer/components/+catalog/custom-views";
|
||||||
import type { StatusBarRegistration } from "../renderer/components/status-bar/status-bar-registration";
|
import type { StatusBarRegistration } from "../renderer/components/status-bar/status-bar-registration";
|
||||||
@ -26,7 +26,7 @@ import { pipeline } from "@ogre-tools/fp";
|
|||||||
import { getExtensionRoutePath } from "../renderer/routes/for-extension";
|
import { getExtensionRoutePath } from "../renderer/routes/for-extension";
|
||||||
import type { LensRendererExtensionDependencies } from "./lens-extension-set-dependencies";
|
import type { LensRendererExtensionDependencies } from "./lens-extension-set-dependencies";
|
||||||
import type { KubeObjectHandlerRegistration } from "../renderer/kube-object/handler";
|
import type { KubeObjectHandlerRegistration } from "../renderer/kube-object/handler";
|
||||||
import type { AppPreferenceTabRegistration } from "../renderer/components/+preferences/app-preference-tab/app-preference-tab-registration";
|
import type { AppPreferenceTabRegistration } from "../features/preferences/renderer/compliance-for-legacy-extension-api/app-preference-tab-registration";
|
||||||
import type { KubeObjectDetailRegistration } from "../renderer/components/kube-object-details/kube-object-detail-registration";
|
import type { KubeObjectDetailRegistration } from "../renderer/components/kube-object-details/kube-object-detail-registration";
|
||||||
|
|
||||||
export class LensRendererExtension extends LensExtension<LensRendererExtensionDependencies> {
|
export class LensRendererExtension extends LensExtension<LensRendererExtensionDependencies> {
|
||||||
|
|||||||
@ -11,43 +11,61 @@ exports[`extension special characters in page registrations renders 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive disabled focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="home"
|
data-testid="home-button"
|
||||||
>
|
>
|
||||||
home
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="home"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
home
|
||||||
data-testid="history-back"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_back"
|
data-testid="history-back"
|
||||||
>
|
>
|
||||||
arrow_back
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_back"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
arrow_back
|
||||||
data-testid="history-forward"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_forward"
|
data-testid="history-forward"
|
||||||
>
|
>
|
||||||
arrow_forward
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
@ -211,44 +229,62 @@ exports[`extension special characters in page registrations when navigating to r
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive focusable"
|
||||||
data-icon-name="home"
|
data-testid="home-button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
home
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="home"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
home
|
||||||
data-testid="history-back"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_back"
|
data-testid="history-back"
|
||||||
>
|
>
|
||||||
arrow_back
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_back"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
arrow_back
|
||||||
data-testid="history-forward"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_forward"
|
data-testid="history-forward"
|
||||||
>
|
>
|
||||||
arrow_forward
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -11,43 +11,61 @@ exports[`navigate to extension page renders 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive disabled focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="home"
|
data-testid="home-button"
|
||||||
>
|
>
|
||||||
home
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="home"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
home
|
||||||
data-testid="history-back"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_back"
|
data-testid="history-back"
|
||||||
>
|
>
|
||||||
arrow_back
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_back"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
arrow_back
|
||||||
data-testid="history-forward"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_forward"
|
data-testid="history-forward"
|
||||||
>
|
>
|
||||||
arrow_forward
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
@ -211,44 +229,62 @@ exports[`navigate to extension page when extension navigates to child route rend
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive focusable"
|
||||||
data-icon-name="home"
|
data-testid="home-button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
home
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="home"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
home
|
||||||
data-testid="history-back"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_back"
|
data-testid="history-back"
|
||||||
>
|
>
|
||||||
arrow_back
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_back"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
arrow_back
|
||||||
data-testid="history-forward"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_forward"
|
data-testid="history-forward"
|
||||||
>
|
>
|
||||||
arrow_forward
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
@ -332,44 +368,62 @@ exports[`navigate to extension page when extension navigates to route with param
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive focusable"
|
||||||
data-icon-name="home"
|
data-testid="home-button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
home
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="home"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
home
|
||||||
data-testid="history-back"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_back"
|
data-testid="history-back"
|
||||||
>
|
>
|
||||||
arrow_back
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_back"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
arrow_back
|
||||||
data-testid="history-forward"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_forward"
|
data-testid="history-forward"
|
||||||
>
|
>
|
||||||
arrow_forward
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
@ -469,44 +523,62 @@ exports[`navigate to extension page when extension navigates to route without pa
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive focusable"
|
||||||
data-icon-name="home"
|
data-testid="home-button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
home
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="home"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
home
|
||||||
data-testid="history-back"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_back"
|
data-testid="history-back"
|
||||||
>
|
>
|
||||||
arrow_back
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_back"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
arrow_back
|
||||||
data-testid="history-forward"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_forward"
|
data-testid="history-forward"
|
||||||
>
|
>
|
||||||
arrow_forward
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
@ -606,44 +678,62 @@ exports[`navigate to extension page when extension navigates to route without pa
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive focusable"
|
||||||
data-icon-name="home"
|
data-testid="home-button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
home
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="home"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
home
|
||||||
data-testid="history-back"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_back"
|
data-testid="history-back"
|
||||||
>
|
>
|
||||||
arrow_back
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_back"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
arrow_back
|
||||||
data-testid="history-forward"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_forward"
|
data-testid="history-forward"
|
||||||
>
|
>
|
||||||
arrow_forward
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -11,44 +11,62 @@ exports[`navigating between routes given route with optional path parameters whe
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive focusable"
|
||||||
data-icon-name="home"
|
data-testid="home-button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
home
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="home"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
home
|
||||||
data-testid="history-back"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_back"
|
data-testid="history-back"
|
||||||
>
|
>
|
||||||
arrow_back
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_back"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
arrow_back
|
||||||
data-testid="history-forward"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_forward"
|
data-testid="history-forward"
|
||||||
>
|
>
|
||||||
arrow_forward
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
@ -135,44 +153,62 @@ exports[`navigating between routes given route without path parameters when navi
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive focusable"
|
||||||
data-icon-name="home"
|
data-testid="home-button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
home
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="home"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
home
|
||||||
data-testid="history-back"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_back"
|
data-testid="history-back"
|
||||||
>
|
>
|
||||||
arrow_back
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_back"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
arrow_back
|
||||||
data-testid="history-forward"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_forward"
|
data-testid="history-forward"
|
||||||
>
|
>
|
||||||
arrow_forward
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -11,43 +11,61 @@ exports[`add-cluster - navigation using application menu renders 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive disabled focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="home"
|
data-testid="home-button"
|
||||||
>
|
>
|
||||||
home
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="home"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
home
|
||||||
data-testid="history-back"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_back"
|
data-testid="history-back"
|
||||||
>
|
>
|
||||||
arrow_back
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_back"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
arrow_back
|
||||||
data-testid="history-forward"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_forward"
|
data-testid="history-forward"
|
||||||
>
|
>
|
||||||
arrow_forward
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
@ -211,44 +229,62 @@ exports[`add-cluster - navigation using application menu when navigating to add
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive focusable"
|
||||||
data-icon-name="home"
|
data-testid="home-button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
home
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="home"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
home
|
||||||
data-testid="history-back"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_back"
|
data-testid="history-back"
|
||||||
>
|
>
|
||||||
arrow_back
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_back"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
arrow_back
|
||||||
data-testid="history-forward"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_forward"
|
data-testid="history-forward"
|
||||||
>
|
>
|
||||||
arrow_forward
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -29,7 +29,11 @@ describe("add-cluster - navigation using application menu", () => {
|
|||||||
|
|
||||||
describe("when navigating to add cluster using application menu", () => {
|
describe("when navigating to add cluster using application menu", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await applicationBuilder.applicationMenu.click("file.add-cluster");
|
await applicationBuilder.applicationMenu.click(
|
||||||
|
"root",
|
||||||
|
"file",
|
||||||
|
"add-cluster",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
|
|||||||
@ -0,0 +1,132 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`application-menu, given platform is 'darwin' given enough time passes populates application menu 1`] = `
|
||||||
|
Array [
|
||||||
|
"root",
|
||||||
|
"root -> mac",
|
||||||
|
"root -> mac -> about",
|
||||||
|
"root -> mac -> separator-1",
|
||||||
|
"root -> mac -> navigate-to-preferences",
|
||||||
|
"root -> mac -> navigate-to-extensions",
|
||||||
|
"root -> mac -> separator-2",
|
||||||
|
"root -> mac -> services",
|
||||||
|
"root -> mac -> separator-3",
|
||||||
|
"root -> mac -> hide",
|
||||||
|
"root -> mac -> hide-others",
|
||||||
|
"root -> mac -> unhide",
|
||||||
|
"root -> mac -> separator-4",
|
||||||
|
"root -> mac -> quit",
|
||||||
|
"root -> file",
|
||||||
|
"root -> file -> add-cluster",
|
||||||
|
"root -> file -> separator-1-for-file",
|
||||||
|
"root -> file -> close-window",
|
||||||
|
"root -> edit",
|
||||||
|
"root -> edit -> undo",
|
||||||
|
"root -> edit -> redo",
|
||||||
|
"root -> edit -> separator-1-in-edit",
|
||||||
|
"root -> edit -> cut",
|
||||||
|
"root -> edit -> copy",
|
||||||
|
"root -> edit -> paste",
|
||||||
|
"root -> edit -> delete",
|
||||||
|
"root -> edit -> separator-2-in-edit",
|
||||||
|
"root -> edit -> selectAll",
|
||||||
|
"root -> view",
|
||||||
|
"root -> view -> navigate-to-catalog",
|
||||||
|
"root -> view -> open-command-palette",
|
||||||
|
"root -> view -> separator-1-for-view",
|
||||||
|
"root -> view -> go-back",
|
||||||
|
"root -> view -> go-forward",
|
||||||
|
"root -> view -> reload",
|
||||||
|
"root -> view -> toggle-dev-tools",
|
||||||
|
"root -> view -> separator-2-for-view",
|
||||||
|
"root -> view -> reset-zoom",
|
||||||
|
"root -> view -> zoom-in",
|
||||||
|
"root -> view -> zoom-out",
|
||||||
|
"root -> view -> separator-3-for-view",
|
||||||
|
"root -> view -> toggle-full-screen",
|
||||||
|
"root -> help",
|
||||||
|
"root -> help -> navigate-to-welcome",
|
||||||
|
"root -> help -> open-documentation",
|
||||||
|
"root -> help -> open-support",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`application-menu, given platform is 'linux' given enough time passes populates application menu 1`] = `
|
||||||
|
Array [
|
||||||
|
"root",
|
||||||
|
"root -> file",
|
||||||
|
"root -> file -> add-cluster",
|
||||||
|
"root -> file -> navigate-to-preferences",
|
||||||
|
"root -> file -> navigate-to-extensions",
|
||||||
|
"root -> file -> quit",
|
||||||
|
"root -> edit",
|
||||||
|
"root -> edit -> undo",
|
||||||
|
"root -> edit -> redo",
|
||||||
|
"root -> edit -> separator-1-in-edit",
|
||||||
|
"root -> edit -> cut",
|
||||||
|
"root -> edit -> copy",
|
||||||
|
"root -> edit -> paste",
|
||||||
|
"root -> edit -> delete",
|
||||||
|
"root -> edit -> separator-2-in-edit",
|
||||||
|
"root -> edit -> selectAll",
|
||||||
|
"root -> view",
|
||||||
|
"root -> view -> navigate-to-catalog",
|
||||||
|
"root -> view -> open-command-palette",
|
||||||
|
"root -> view -> separator-1-for-view",
|
||||||
|
"root -> view -> go-back",
|
||||||
|
"root -> view -> go-forward",
|
||||||
|
"root -> view -> reload",
|
||||||
|
"root -> view -> toggle-dev-tools",
|
||||||
|
"root -> view -> separator-2-for-view",
|
||||||
|
"root -> view -> reset-zoom",
|
||||||
|
"root -> view -> zoom-in",
|
||||||
|
"root -> view -> zoom-out",
|
||||||
|
"root -> view -> separator-3-for-view",
|
||||||
|
"root -> view -> toggle-full-screen",
|
||||||
|
"root -> help",
|
||||||
|
"root -> help -> navigate-to-welcome",
|
||||||
|
"root -> help -> open-documentation",
|
||||||
|
"root -> help -> open-support",
|
||||||
|
"root -> help -> about",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`application-menu, given platform is 'win32' given enough time passes populates application menu 1`] = `
|
||||||
|
Array [
|
||||||
|
"root",
|
||||||
|
"root -> file",
|
||||||
|
"root -> file -> add-cluster",
|
||||||
|
"root -> file -> navigate-to-preferences",
|
||||||
|
"root -> file -> navigate-to-extensions",
|
||||||
|
"root -> file -> quit",
|
||||||
|
"root -> edit",
|
||||||
|
"root -> edit -> undo",
|
||||||
|
"root -> edit -> redo",
|
||||||
|
"root -> edit -> separator-1-in-edit",
|
||||||
|
"root -> edit -> cut",
|
||||||
|
"root -> edit -> copy",
|
||||||
|
"root -> edit -> paste",
|
||||||
|
"root -> edit -> delete",
|
||||||
|
"root -> edit -> separator-2-in-edit",
|
||||||
|
"root -> edit -> selectAll",
|
||||||
|
"root -> view",
|
||||||
|
"root -> view -> navigate-to-catalog",
|
||||||
|
"root -> view -> open-command-palette",
|
||||||
|
"root -> view -> separator-1-for-view",
|
||||||
|
"root -> view -> go-back",
|
||||||
|
"root -> view -> go-forward",
|
||||||
|
"root -> view -> reload",
|
||||||
|
"root -> view -> toggle-dev-tools",
|
||||||
|
"root -> view -> separator-2-for-view",
|
||||||
|
"root -> view -> reset-zoom",
|
||||||
|
"root -> view -> zoom-in",
|
||||||
|
"root -> view -> zoom-out",
|
||||||
|
"root -> view -> separator-3-for-view",
|
||||||
|
"root -> view -> toggle-full-screen",
|
||||||
|
"root -> help",
|
||||||
|
"root -> help -> navigate-to-welcome",
|
||||||
|
"root -> help -> open-documentation",
|
||||||
|
"root -> help -> open-support",
|
||||||
|
"root -> help -> about",
|
||||||
|
]
|
||||||
|
`;
|
||||||
@ -0,0 +1,226 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { noop } from "lodash/fp";
|
||||||
|
import { runInAction } from "mobx";
|
||||||
|
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
|
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
|
import type { FakeExtensionOptions } from "../../renderer/components/test-utils/get-extension-fake";
|
||||||
|
import applicationMenuItemInjectionToken from "./main/menu-items/application-menu-item-injection-token";
|
||||||
|
import logErrorInjectable from "../../common/log-error.injectable";
|
||||||
|
|
||||||
|
describe("application-menu-in-legacy-extension-api", () => {
|
||||||
|
let builder: ApplicationBuilder;
|
||||||
|
let logErrorMock: jest.Mock;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
builder = getApplicationBuilder();
|
||||||
|
|
||||||
|
builder.beforeApplicationStart(
|
||||||
|
(mainDi) => {
|
||||||
|
runInAction(() => {
|
||||||
|
mainDi.register(
|
||||||
|
someTopMenuItemInjectable,
|
||||||
|
someNonExtensionBasedMenuItemInjectable,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
logErrorMock = jest.fn();
|
||||||
|
|
||||||
|
mainDi.override(logErrorInjectable, () => logErrorMock);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await builder.startHidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when extension with application menu items is enabled", () => {
|
||||||
|
let onClickMock: jest.Mock;
|
||||||
|
let testExtensionOptions: FakeExtensionOptions;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
onClickMock = jest.fn();
|
||||||
|
|
||||||
|
testExtensionOptions = {
|
||||||
|
id: "some-test-extension",
|
||||||
|
name: "some-extension-name",
|
||||||
|
|
||||||
|
mainOptions: {
|
||||||
|
appMenus: [
|
||||||
|
{
|
||||||
|
id: "some-non-shown-item",
|
||||||
|
parentId: "some-top-menu-item",
|
||||||
|
click: noop,
|
||||||
|
label: "Irrelevant",
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: "some-clickable-item",
|
||||||
|
parentId: "some-top-menu-item",
|
||||||
|
click: onClickMock,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
parentId: "some-top-menu-item",
|
||||||
|
type: "separator",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: "some-os-action-menu-item-id",
|
||||||
|
parentId: "some-top-menu-item",
|
||||||
|
role: "help",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: "some-submenu-with-explicit-children",
|
||||||
|
parentId: "some-top-menu-item",
|
||||||
|
|
||||||
|
submenu: [
|
||||||
|
{ id: "some-explicit-child", label: "Some explicit child", click: noop },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
builder.extensions.enable(testExtensionOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("related menu items exist", () => {
|
||||||
|
const menuItemPathsForExtension = builder.applicationMenu.items.filter(
|
||||||
|
(x) =>
|
||||||
|
x.join(".").startsWith("root.some-top-menu-item.some-extension-name"),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(menuItemPathsForExtension).toEqual([
|
||||||
|
["root", "some-top-menu-item", "some-extension-name/some-clickable-item"],
|
||||||
|
// Note: anonymous index "1" is used by the non-visible menu item.
|
||||||
|
["root", "some-top-menu-item", "some-extension-name/2-separator"],
|
||||||
|
["root", "some-top-menu-item", "some-extension-name/some-os-action-menu-item-id"],
|
||||||
|
["root", "some-top-menu-item", "some-extension-name/some-submenu-with-explicit-children"],
|
||||||
|
["root", "some-top-menu-item", "some-extension-name/some-submenu-with-explicit-children", "some-extension-name/some-submenu-with-explicit-children/some-explicit-child"],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when the extension-based clickable menu item is clicked, does so", () => {
|
||||||
|
builder.applicationMenu.click(
|
||||||
|
"root", "some-top-menu-item", "some-extension-name/some-clickable-item",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onClickMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the extension is disabled", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
builder.extensions.disable(testExtensionOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when related menu items no longer exist", () => {
|
||||||
|
const menuItemPathsForExtension = builder.applicationMenu.items.filter(
|
||||||
|
(x) =>
|
||||||
|
x.join(".").startsWith("root.some-top-menu-item.some-extension-name"),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(menuItemPathsForExtension).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when the extension is enabled again, also related menu items exist again", () => {
|
||||||
|
builder.extensions.enable(testExtensionOptions);
|
||||||
|
|
||||||
|
const menuItemPathsForExtension = builder.applicationMenu.items.filter(
|
||||||
|
(x) =>
|
||||||
|
x.join(".").startsWith("root.some-top-menu-item.some-extension-name"),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(menuItemPathsForExtension).toEqual([
|
||||||
|
["root", "some-top-menu-item", "some-extension-name/some-clickable-item"],
|
||||||
|
["root", "some-top-menu-item", "some-extension-name/2-separator"],
|
||||||
|
["root", "some-top-menu-item", "some-extension-name/some-os-action-menu-item-id"],
|
||||||
|
["root", "some-top-menu-item", "some-extension-name/some-submenu-with-explicit-children"],
|
||||||
|
["root", "some-top-menu-item", "some-extension-name/some-submenu-with-explicit-children", "some-extension-name/some-submenu-with-explicit-children/some-explicit-child"],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when extension with unrecognizable application menu items is enabled", () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const testExtensionOptions: FakeExtensionOptions = {
|
||||||
|
id: "some-test-extension",
|
||||||
|
name: "some-extension-name",
|
||||||
|
|
||||||
|
mainOptions: {
|
||||||
|
appMenus: [
|
||||||
|
{
|
||||||
|
id: "some-recognizable-item",
|
||||||
|
parentId: "some-top-menu-item",
|
||||||
|
click: noop,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: "some-unrecognizable-item",
|
||||||
|
parentId: "some-top-menu-item",
|
||||||
|
// Note: there is no way to recognize this
|
||||||
|
// click: noop,
|
||||||
|
// role: "help"
|
||||||
|
// submenu: []
|
||||||
|
// type: "separator"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
builder.extensions.enable(testExtensionOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("only recognizable menu items from extension exist", () => {
|
||||||
|
const menuItemPathsForExtension = builder.applicationMenu.items.filter(
|
||||||
|
(x) =>
|
||||||
|
x.join(".").startsWith("root.some-top-menu-item.some-extension-name"),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(menuItemPathsForExtension).toEqual([
|
||||||
|
["root", "some-top-menu-item", "some-extension-name/some-recognizable-item"],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logs about the unrecognizable item", () => {
|
||||||
|
expect(logErrorMock).toHaveBeenCalledWith(
|
||||||
|
'[MENU]: Tried to register menu item "some-extension-name/some-unrecognizable-item" but it is not recognizable as any of ApplicationMenuItemTypes',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const someTopMenuItemInjectable = getInjectable({
|
||||||
|
id: "some-top-menu-item",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
id: "some-top-menu-item",
|
||||||
|
parentId: "root" as const,
|
||||||
|
kind: "top-level-menu" as const,
|
||||||
|
label: "Some existing root menu item",
|
||||||
|
orderNumber: 42,
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const someNonExtensionBasedMenuItemInjectable = getInjectable({
|
||||||
|
id: "some-non-extension-based-menu-item",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
id: "some-non-extension-based-menu-item",
|
||||||
|
parentId: "some-top-menu-item",
|
||||||
|
kind: "clickable-menu-item" as const,
|
||||||
|
label: "Some menu item",
|
||||||
|
onClick: () => {},
|
||||||
|
orderNumber: 42,
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
59
src/features/application-menu/application-menu.test.ts
Normal file
59
src/features/application-menu/application-menu.test.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
|
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
|
import populateApplicationMenuInjectable from "./main/populate-application-menu.injectable";
|
||||||
|
import { advanceFakeTime, useFakeTime } from "../../common/test-utils/use-fake-time";
|
||||||
|
import { getCompositePaths } from "../../common/utils/composite/get-composite-paths/get-composite-paths";
|
||||||
|
import platformInjectable, { allPlatforms } from "../../common/vars/platform.injectable";
|
||||||
|
|
||||||
|
describe.each(allPlatforms)("application-menu, given platform is '%s'", (platform) => {
|
||||||
|
let builder: ApplicationBuilder;
|
||||||
|
let populateApplicationMenuMock: jest.Mock;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
useFakeTime();
|
||||||
|
|
||||||
|
populateApplicationMenuMock = jest.fn();
|
||||||
|
|
||||||
|
builder = getApplicationBuilder();
|
||||||
|
|
||||||
|
builder.beforeApplicationStart((mainDi) => {
|
||||||
|
mainDi.override(platformInjectable, () => platform);
|
||||||
|
|
||||||
|
mainDi.override(
|
||||||
|
populateApplicationMenuInjectable,
|
||||||
|
() => populateApplicationMenuMock,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await builder.startHidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when insufficient time passes, does not populate menu items yet", () => {
|
||||||
|
advanceFakeTime(99);
|
||||||
|
|
||||||
|
expect(populateApplicationMenuMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given enough time passes", () => {
|
||||||
|
let applicationMenuPaths: string[][];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
advanceFakeTime(100);
|
||||||
|
applicationMenuPaths = getCompositePaths(
|
||||||
|
populateApplicationMenuMock.mock.calls[0][0],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("populates application menu with at least something", () => {
|
||||||
|
expect(applicationMenuPaths.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("populates application menu", () => {
|
||||||
|
expect(applicationMenuPaths.map(x => x.join(" -> "))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
|
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
|
import populateApplicationMenuInjectable from "./main/populate-application-menu.injectable";
|
||||||
|
import { advanceFakeTime, useFakeTime } from "../../common/test-utils/use-fake-time";
|
||||||
|
import { getCompositePaths } from "../../common/utils/composite/get-composite-paths/get-composite-paths";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "./main/menu-items/application-menu-item-injection-token";
|
||||||
|
import { runInAction } from "mobx";
|
||||||
|
import logErrorInjectable from "../../common/log-error.injectable";
|
||||||
|
|
||||||
|
describe("handling-of-orphan-application-menu-items, given orphan menu item", () => {
|
||||||
|
let builder: ApplicationBuilder;
|
||||||
|
let populateApplicationMenuMock: jest.Mock;
|
||||||
|
let logErrorMock: jest.Mock;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
useFakeTime();
|
||||||
|
|
||||||
|
populateApplicationMenuMock = jest.fn();
|
||||||
|
logErrorMock = jest.fn();
|
||||||
|
|
||||||
|
builder = getApplicationBuilder();
|
||||||
|
|
||||||
|
builder.beforeApplicationStart((mainDi) => {
|
||||||
|
const someOrphanMenuItemInjectable = getInjectable({
|
||||||
|
id: "some-orphan-menu-item",
|
||||||
|
instantiate: () => ({
|
||||||
|
kind: "sub-menu" as const,
|
||||||
|
id: "some-item-id",
|
||||||
|
// Note: unknown id makes this item an orphan.
|
||||||
|
parentId: "some-unknown-parent-id",
|
||||||
|
orderNumber: 0,
|
||||||
|
label: "irrelevant",
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
mainDi.register(someOrphanMenuItemInjectable);
|
||||||
|
});
|
||||||
|
|
||||||
|
mainDi.override(logErrorInjectable, () => logErrorMock);
|
||||||
|
|
||||||
|
mainDi.override(
|
||||||
|
populateApplicationMenuInjectable,
|
||||||
|
() => populateApplicationMenuMock,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await builder.startHidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given some time passes", () => {
|
||||||
|
let applicationMenuPaths: string[][];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
advanceFakeTime(100);
|
||||||
|
|
||||||
|
applicationMenuPaths = getCompositePaths(
|
||||||
|
populateApplicationMenuMock.mock.calls[0][0],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps showing the other application menu items without throwing", () => {
|
||||||
|
expect(applicationMenuPaths.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not show orphan application menu item", () => {
|
||||||
|
expect(applicationMenuPaths.find(x => x.join(".").endsWith("some-item-id")));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logs about bad menu item", () => {
|
||||||
|
expect(logErrorMock).toHaveBeenCalledWith('[MENU]: cannot render menu item for missing parentIds: "some-unknown-parent-id"');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemsInjectable from "./application-menu-items.injectable";
|
||||||
|
import type { Composite } from "../../../common/utils/composite/get-composite/get-composite";
|
||||||
|
import { getCompositeFor } from "../../../common/utils/composite/get-composite/get-composite";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import { pipeline } from "@ogre-tools/fp";
|
||||||
|
import type { ApplicationMenuItemTypes } from "./menu-items/application-menu-item-injection-token";
|
||||||
|
import type { RootComposite } from "../../../common/utils/composite/interfaces";
|
||||||
|
import type { Discriminable } from "../../../common/utils/composable-responsibilities/discriminable/discriminable";
|
||||||
|
import { orderByOrderNumber } from "../../../common/utils/composable-responsibilities/orderable/orderable";
|
||||||
|
import logErrorInjectable from "../../../common/log-error.injectable";
|
||||||
|
import { isShown } from "../../../common/utils/composable-responsibilities/showable/showable";
|
||||||
|
|
||||||
|
export type MenuItemRoot = Discriminable<"root"> & RootComposite<"root">;
|
||||||
|
|
||||||
|
const applicationMenuItemCompositeInjectable = getInjectable({
|
||||||
|
id: "application-menu-item-composite",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const menuItems = di.inject(applicationMenuItemsInjectable);
|
||||||
|
const logError = di.inject(logErrorInjectable);
|
||||||
|
|
||||||
|
return computed((): Composite<ApplicationMenuItemTypes | MenuItemRoot> => {
|
||||||
|
const items = menuItems.get();
|
||||||
|
|
||||||
|
return pipeline(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
parentId: undefined,
|
||||||
|
id: "root",
|
||||||
|
kind: "root",
|
||||||
|
} as const,
|
||||||
|
|
||||||
|
...items,
|
||||||
|
],
|
||||||
|
|
||||||
|
getCompositeFor({
|
||||||
|
getId: (x) => x.id,
|
||||||
|
getParentId: (x) => x.parentId,
|
||||||
|
transformChildren: (children) =>
|
||||||
|
pipeline(
|
||||||
|
children,
|
||||||
|
orderByOrderNumber,
|
||||||
|
(children) => children.filter(isShown),
|
||||||
|
),
|
||||||
|
|
||||||
|
handleMissingParentIds: ({ missingParentIds }) => {
|
||||||
|
logError(
|
||||||
|
`[MENU]: cannot render menu item for missing parentIds: "${missingParentIds.join(
|
||||||
|
'", "',
|
||||||
|
)}"`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default applicationMenuItemCompositeInjectable;
|
||||||
@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import type { Injectable } from "@ogre-tools/injectable";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { extensionRegistratorInjectionToken } from "../../../extensions/extension-loader/extension-registrator-injection-token";
|
||||||
|
import type { LensExtension } from "../../../extensions/lens-extension";
|
||||||
|
import type { LensMainExtension } from "../../../extensions/lens-main-extension";
|
||||||
|
import type {
|
||||||
|
ApplicationMenuItemTypes,
|
||||||
|
ClickableMenuItem,
|
||||||
|
OsActionMenuItem,
|
||||||
|
Separator,
|
||||||
|
} from "./menu-items/application-menu-item-injection-token";
|
||||||
|
import applicationMenuItemInjectionToken from "./menu-items/application-menu-item-injection-token";
|
||||||
|
import type { MenuRegistration } from "./menu-registration";
|
||||||
|
import logErrorInjectable from "../../../common/log-error.injectable";
|
||||||
|
|
||||||
|
const applicationMenuItemRegistratorInjectable = getInjectable({
|
||||||
|
id: "application-menu-item-registrator",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const logError = di.inject(logErrorInjectable);
|
||||||
|
const toRecursedInjectables = toRecursedInjectablesFor(logError);
|
||||||
|
|
||||||
|
return (ext: LensExtension) => {
|
||||||
|
const extension = ext as LensMainExtension;
|
||||||
|
|
||||||
|
return extension.appMenus.flatMap(
|
||||||
|
toRecursedInjectables([extension.sanitizedExtensionId]),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: extensionRegistratorInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default applicationMenuItemRegistratorInjectable;
|
||||||
|
|
||||||
|
const toRecursedInjectablesFor = (logError: (errorMessage: string) => void) => {
|
||||||
|
const toRecursedInjectables = (previousIdPath: string[]) =>
|
||||||
|
(
|
||||||
|
registration: MenuRegistration,
|
||||||
|
index: number,
|
||||||
|
// Todo: new version of injectable would require less type parameters with defaults.
|
||||||
|
): Injectable<
|
||||||
|
ApplicationMenuItemTypes,
|
||||||
|
ApplicationMenuItemTypes,
|
||||||
|
void
|
||||||
|
>[] => {
|
||||||
|
const previousIdPathString = previousIdPath.join("/");
|
||||||
|
const registrationId = registration.id || index.toString();
|
||||||
|
const currentIdPath = [...previousIdPath, registrationId];
|
||||||
|
const currentIdPathString = currentIdPath.join("/");
|
||||||
|
const parentId = registration.parentId || previousIdPathString;
|
||||||
|
|
||||||
|
const menuItem = getApplicationMenuItem({
|
||||||
|
registration,
|
||||||
|
parentId,
|
||||||
|
currentIdPathString,
|
||||||
|
index,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!menuItem) {
|
||||||
|
logError(`[MENU]: Tried to register menu item "${currentIdPathString}" but it is not recognizable as any of ApplicationMenuItemTypes`);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
getInjectable({
|
||||||
|
id: `${currentIdPathString}/application-menu-item`,
|
||||||
|
|
||||||
|
instantiate: () => menuItem,
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
}),
|
||||||
|
|
||||||
|
...((registration.submenu as MenuRegistration[])
|
||||||
|
? (registration.submenu as MenuRegistration[]).flatMap(
|
||||||
|
toRecursedInjectables(currentIdPath),
|
||||||
|
)
|
||||||
|
: []),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
return toRecursedInjectables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getApplicationMenuItem = ({
|
||||||
|
registration,
|
||||||
|
index,
|
||||||
|
currentIdPathString,
|
||||||
|
parentId,
|
||||||
|
}: {
|
||||||
|
registration: MenuRegistration;
|
||||||
|
index: number;
|
||||||
|
currentIdPathString: string;
|
||||||
|
parentId: string;
|
||||||
|
}): ApplicationMenuItemTypes | undefined => {
|
||||||
|
const orderNumber = 1000 + index * 10;
|
||||||
|
|
||||||
|
if (registration.type === "separator") {
|
||||||
|
return {
|
||||||
|
kind: "separator" as const,
|
||||||
|
id: `${currentIdPathString}-separator`,
|
||||||
|
parentId,
|
||||||
|
orderNumber,
|
||||||
|
} as Separator;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registration.submenu) {
|
||||||
|
return {
|
||||||
|
kind: "sub-menu" as const,
|
||||||
|
id: currentIdPathString,
|
||||||
|
parentId,
|
||||||
|
isShown: registration.visible ?? true,
|
||||||
|
orderNumber,
|
||||||
|
label: registration.label || "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registration.click) {
|
||||||
|
return {
|
||||||
|
kind: "clickable-menu-item" as const,
|
||||||
|
id: currentIdPathString,
|
||||||
|
parentId,
|
||||||
|
// Todo: hide electron events from this abstraction.
|
||||||
|
onClick: registration.click,
|
||||||
|
label: registration.label,
|
||||||
|
isShown: registration.visible ?? true,
|
||||||
|
orderNumber,
|
||||||
|
|
||||||
|
...(registration.accelerator
|
||||||
|
? { keyboardShortcut: registration.accelerator as string }
|
||||||
|
: {}),
|
||||||
|
} as ClickableMenuItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registration.role) {
|
||||||
|
return {
|
||||||
|
kind: "os-action-menu-item" as const,
|
||||||
|
id: currentIdPathString,
|
||||||
|
parentId,
|
||||||
|
label: registration.label,
|
||||||
|
isShown: registration.visible ?? true,
|
||||||
|
orderNumber,
|
||||||
|
actionName: registration.role,
|
||||||
|
|
||||||
|
...(registration.accelerator
|
||||||
|
? { keyboardShortcut: registration.accelerator as string }
|
||||||
|
: {}),
|
||||||
|
} as OsActionMenuItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { MenuItemConstructorOptions } from "electron";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import applicationMenuItemInjectionToken from "./menu-items/application-menu-item-injection-token";
|
||||||
|
import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
|
||||||
|
|
||||||
|
export interface MenuItemOpts extends MenuItemConstructorOptions {
|
||||||
|
submenu?: MenuItemConstructorOptions[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const applicationMenuItemsInjectable = getInjectable({
|
||||||
|
id: "application-menu-items",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const computedInjectMany = di.inject(computedInjectManyInjectable);
|
||||||
|
|
||||||
|
return computed(() =>
|
||||||
|
computedInjectMany(applicationMenuItemInjectionToken).get(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export default applicationMenuItemsInjectable;
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { autorun } from "mobx";
|
||||||
|
import { getStartableStoppable } from "../../../common/utils/get-startable-stoppable";
|
||||||
|
import populateApplicationMenuInjectable from "./populate-application-menu.injectable";
|
||||||
|
import applicationMenuItemCompositeInjectable from "./application-menu-item-composite.injectable";
|
||||||
|
|
||||||
|
const applicationMenuReactivityInjectable = getInjectable({
|
||||||
|
id: "application-menu-reactivity",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const applicationMenuItemComposite = di.inject(applicationMenuItemCompositeInjectable);
|
||||||
|
const populateApplicationMenu = di.inject(populateApplicationMenuInjectable);
|
||||||
|
|
||||||
|
return getStartableStoppable("application-menu-reactivity", () =>
|
||||||
|
autorun(() => populateApplicationMenu(applicationMenuItemComposite.get()), {
|
||||||
|
delay: 100,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default applicationMenuReactivityInjectable;
|
||||||
@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
import type { BrowserWindow, KeyboardEvent, MenuItemConstructorOptions, MenuItem as ElectronMenuItem } from "electron";
|
||||||
|
import type { SetOptional } from "type-fest";
|
||||||
|
import type { ChildOfParentComposite, ParentOfChildComposite } from "../../../../common/utils/composite/interfaces";
|
||||||
|
import type { MaybeShowable } from "../../../../common/utils/composable-responsibilities/showable/showable";
|
||||||
|
import type { Discriminable } from "../../../../common/utils/composable-responsibilities/discriminable/discriminable";
|
||||||
|
import type { Orderable } from "../../../../common/utils/composable-responsibilities/orderable/orderable";
|
||||||
|
|
||||||
|
export interface MayHaveKeyboardShortcut {
|
||||||
|
keyboardShortcut?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ElectronClickable {
|
||||||
|
// TODO: This leaky abstraction is exposed in Extension API, therefore cannot be updated
|
||||||
|
onClick: (menuItem: ElectronMenuItem, browserWindow: (BrowserWindow) | (undefined), event: KeyboardEvent) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Labeled {
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MaybeLabeled extends SetOptional<Labeled, "label"> {}
|
||||||
|
|
||||||
|
type ApplicationMenuItemType<T extends string> =
|
||||||
|
// Note: "kind" is being used for Discriminated unions of TypeScript to achieve type narrowing.
|
||||||
|
// See: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions
|
||||||
|
& Discriminable<T>
|
||||||
|
& ParentOfChildComposite
|
||||||
|
& ChildOfParentComposite
|
||||||
|
& MaybeShowable
|
||||||
|
& Orderable;
|
||||||
|
|
||||||
|
export type TopLevelMenu =
|
||||||
|
& ApplicationMenuItemType<"top-level-menu">
|
||||||
|
& { parentId: "root" }
|
||||||
|
& Labeled
|
||||||
|
& MayHaveElectronRole;
|
||||||
|
|
||||||
|
interface MayHaveElectronRole {
|
||||||
|
role?: ElectronRoles;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ElectronRoles = Exclude<MenuItemConstructorOptions["role"], undefined>;
|
||||||
|
|
||||||
|
export type SubMenu =
|
||||||
|
& ApplicationMenuItemType<"sub-menu">
|
||||||
|
& Labeled
|
||||||
|
& ChildOfParentComposite;
|
||||||
|
|
||||||
|
export type ClickableMenuItem =
|
||||||
|
& ApplicationMenuItemType<"clickable-menu-item">
|
||||||
|
& MenuItem
|
||||||
|
& Labeled
|
||||||
|
& ElectronClickable;
|
||||||
|
|
||||||
|
export type OsActionMenuItem =
|
||||||
|
& ApplicationMenuItemType<"os-action-menu-item">
|
||||||
|
& MenuItem
|
||||||
|
& MaybeLabeled
|
||||||
|
& TriggersElectronAction;
|
||||||
|
|
||||||
|
type MenuItem =
|
||||||
|
& ChildOfParentComposite
|
||||||
|
& MayHaveKeyboardShortcut;
|
||||||
|
|
||||||
|
interface TriggersElectronAction {
|
||||||
|
actionName: ElectronRoles;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Todo: SeparatorMenuItem
|
||||||
|
export type Separator =
|
||||||
|
& ApplicationMenuItemType<"separator">
|
||||||
|
& ChildOfParentComposite;
|
||||||
|
|
||||||
|
export type ApplicationMenuItemTypes =
|
||||||
|
| TopLevelMenu
|
||||||
|
| SubMenu
|
||||||
|
| OsActionMenuItem
|
||||||
|
| ClickableMenuItem
|
||||||
|
| Separator
|
||||||
|
;
|
||||||
|
|
||||||
|
const applicationMenuItemInjectionToken = getInjectionToken<ApplicationMenuItemTypes>({
|
||||||
|
id: "application-menu-item-injection-token",
|
||||||
|
});
|
||||||
|
|
||||||
|
export default applicationMenuItemInjectionToken;
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../application-menu-item-injection-token";
|
||||||
|
|
||||||
|
const editMenuItemInjectable = getInjectable({
|
||||||
|
id: "edit-application-menu-item",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
kind: "top-level-menu" as const,
|
||||||
|
id: "edit",
|
||||||
|
parentId: "root" as const,
|
||||||
|
orderNumber: 30,
|
||||||
|
label: "Edit",
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default editMenuItemInjectable;
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getApplicationMenuOperationSystemActionInjectable } from "../../get-application-menu-operation-system-action-injectable";
|
||||||
|
import { getApplicationMenuSeparatorInjectable } from "../../get-application-menu-separator-injectable";
|
||||||
|
|
||||||
|
export const actionForUndo = getApplicationMenuOperationSystemActionInjectable({
|
||||||
|
id: "undo",
|
||||||
|
parentId: "edit",
|
||||||
|
orderNumber: 10,
|
||||||
|
actionName: "undo",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const actionForRedo = getApplicationMenuOperationSystemActionInjectable({
|
||||||
|
id: "redo",
|
||||||
|
parentId: "edit",
|
||||||
|
orderNumber: 20,
|
||||||
|
actionName: "redo",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const separator1 = getApplicationMenuSeparatorInjectable({
|
||||||
|
id: "separator-1-in-edit",
|
||||||
|
parentId: "edit",
|
||||||
|
orderNumber: 30,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const actionForCut = getApplicationMenuOperationSystemActionInjectable({
|
||||||
|
id: "cut",
|
||||||
|
parentId: "edit",
|
||||||
|
orderNumber: 40,
|
||||||
|
actionName: "cut",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const actionForCopy = getApplicationMenuOperationSystemActionInjectable({
|
||||||
|
id: "copy",
|
||||||
|
parentId: "edit",
|
||||||
|
orderNumber: 50,
|
||||||
|
actionName: "copy",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const actionForPaste = getApplicationMenuOperationSystemActionInjectable({
|
||||||
|
id: "paste",
|
||||||
|
parentId: "edit",
|
||||||
|
orderNumber: 60,
|
||||||
|
actionName: "paste",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const actionForDelete = getApplicationMenuOperationSystemActionInjectable({
|
||||||
|
id: "delete",
|
||||||
|
parentId: "edit",
|
||||||
|
orderNumber: 70,
|
||||||
|
actionName: "delete",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const separator2 = getApplicationMenuSeparatorInjectable({
|
||||||
|
id: "separator-2-in-edit",
|
||||||
|
parentId: "edit",
|
||||||
|
orderNumber: 80,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const actionForSelectAll = getApplicationMenuOperationSystemActionInjectable({
|
||||||
|
id: "selectAll",
|
||||||
|
parentId: "edit",
|
||||||
|
orderNumber: 90,
|
||||||
|
actionName: "selectAll",
|
||||||
|
});
|
||||||
|
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token";
|
||||||
|
import navigateToAddClusterInjectable from "../../../../../../common/front-end-routing/routes/add-cluster/navigate-to-add-cluster.injectable";
|
||||||
|
|
||||||
|
const addClusterMenuItemInjectable = getInjectable({
|
||||||
|
id: "add-cluster-application-menu-item",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const navigateToAddCluster = di.inject(navigateToAddClusterInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: "clickable-menu-item" as const,
|
||||||
|
parentId: "file",
|
||||||
|
id: "add-cluster",
|
||||||
|
orderNumber: 10,
|
||||||
|
label: "Add Cluster",
|
||||||
|
keyboardShortcut: "CmdOrCtrl+Shift+A",
|
||||||
|
|
||||||
|
onClick: () => {
|
||||||
|
navigateToAddCluster();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default addClusterMenuItemInjectable;
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token";
|
||||||
|
import isMacInjectable from "../../../../../../common/vars/is-mac.injectable";
|
||||||
|
|
||||||
|
const closeWindowMenuItemInjectable = getInjectable({
|
||||||
|
id: "close-window-application-menu-item",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const isMac = di.inject(isMacInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: "close-window",
|
||||||
|
kind: "os-action-menu-item" as const,
|
||||||
|
parentId: "file",
|
||||||
|
orderNumber: 60,
|
||||||
|
actionName: "close" as const,
|
||||||
|
label: "Close Window",
|
||||||
|
keyboardShortcut: "Shift+Cmd+W",
|
||||||
|
isShown: isMac,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default closeWindowMenuItemInjectable;
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../application-menu-item-injection-token";
|
||||||
|
|
||||||
|
const fileMenuItemInjectable = getInjectable({
|
||||||
|
id: "file-application-menu-item",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
kind: "top-level-menu" as const,
|
||||||
|
id: "file",
|
||||||
|
parentId: "root" as const,
|
||||||
|
orderNumber: 20,
|
||||||
|
label: "File",
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default fileMenuItemInjectable;
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
getApplicationMenuSeparatorInjectable,
|
||||||
|
} from "../../get-application-menu-separator-injectable";
|
||||||
|
|
||||||
|
export const separator1 = getApplicationMenuSeparatorInjectable({
|
||||||
|
id: "separator-1-for-file",
|
||||||
|
parentId: "file",
|
||||||
|
orderNumber: 20,
|
||||||
|
isShownOnlyOnMac: true,
|
||||||
|
});
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { OsActionMenuItem } from "./application-menu-item-injection-token";
|
||||||
|
import applicationMenuItemInjectionToken from "./application-menu-item-injection-token";
|
||||||
|
|
||||||
|
const getApplicationMenuOperationSystemActionInjectable = ({
|
||||||
|
id,
|
||||||
|
...rest
|
||||||
|
}: Omit<OsActionMenuItem, "kind" >) =>
|
||||||
|
getInjectable({
|
||||||
|
id: `application-menu-operation-system-action/${id}`,
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
...rest,
|
||||||
|
id,
|
||||||
|
kind: "os-action-menu-item" as const,
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export { getApplicationMenuOperationSystemActionInjectable };
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { Separator } from "./application-menu-item-injection-token";
|
||||||
|
import applicationMenuItemInjectionToken from "./application-menu-item-injection-token";
|
||||||
|
import isMacInjectable from "../../../../common/vars/is-mac.injectable";
|
||||||
|
|
||||||
|
const getApplicationMenuSeparatorInjectable = ({
|
||||||
|
id,
|
||||||
|
isShownOnlyOnMac = false,
|
||||||
|
...rest
|
||||||
|
}: { isShownOnlyOnMac?: boolean } & Omit<
|
||||||
|
Separator,
|
||||||
|
"kind" | "isShown"
|
||||||
|
>) =>
|
||||||
|
getInjectable({
|
||||||
|
id: `application-menu-separator/${id}`,
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const isMac = di.inject(isMacInjectable);
|
||||||
|
const isShown = isShownOnlyOnMac ? isMac : true;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
id,
|
||||||
|
kind: "separator" as const,
|
||||||
|
isShown,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export { getApplicationMenuSeparatorInjectable };
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../application-menu-item-injection-token";
|
||||||
|
|
||||||
|
const helpMenuItemInjectable = getInjectable({
|
||||||
|
id: "help-application-menu-item",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
kind: "top-level-menu" as const,
|
||||||
|
id: "help",
|
||||||
|
parentId: "root" as const,
|
||||||
|
orderNumber: 50,
|
||||||
|
label: "Help",
|
||||||
|
role: "help" as const,
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default helpMenuItemInjectable;
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token";
|
||||||
|
import navigateToWelcomeInjectable from "../../../../../../common/front-end-routing/routes/welcome/navigate-to-welcome.injectable";
|
||||||
|
|
||||||
|
const navigateToWelcomeMenuItem = getInjectable({
|
||||||
|
id: "navigate-to-welcome-menu-item",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const navigateToWelcome = di.inject(navigateToWelcomeInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: "clickable-menu-item" as const,
|
||||||
|
parentId: "help",
|
||||||
|
id: "navigate-to-welcome",
|
||||||
|
orderNumber: 10,
|
||||||
|
label: "Welcome",
|
||||||
|
|
||||||
|
onClick: () => {
|
||||||
|
navigateToWelcome();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default navigateToWelcomeMenuItem;
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token";
|
||||||
|
import { docsUrl } from "../../../../../../common/vars";
|
||||||
|
import openLinkInBrowserInjectable from "../../../../../../common/utils/open-link-in-browser.injectable";
|
||||||
|
import loggerInjectable from "../../../../../../common/logger.injectable";
|
||||||
|
|
||||||
|
const openDocumentationMenuItemInjectable = getInjectable({
|
||||||
|
id: "open-documentation-menu-item",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const openLinkInBrowser = di.inject(openLinkInBrowserInjectable);
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: "clickable-menu-item" as const,
|
||||||
|
parentId: "help",
|
||||||
|
id: "open-documentation",
|
||||||
|
orderNumber: 20,
|
||||||
|
label: "Documentation",
|
||||||
|
|
||||||
|
// TODO: Convert to async/await
|
||||||
|
onClick: () => {
|
||||||
|
openLinkInBrowser(docsUrl).catch((error) => {
|
||||||
|
logger.error("[MENU]: failed to open browser", { error });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default openDocumentationMenuItemInjectable;
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token";
|
||||||
|
import { supportUrl } from "../../../../../../common/vars";
|
||||||
|
import openLinkInBrowserInjectable from "../../../../../../common/utils/open-link-in-browser.injectable";
|
||||||
|
import loggerInjectable from "../../../../../../common/logger.injectable";
|
||||||
|
|
||||||
|
const openSupportItemInjectable = getInjectable({
|
||||||
|
id: "open-support-menu-item",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const openLinkInBrowser = di.inject(openLinkInBrowserInjectable);
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: "clickable-menu-item" as const,
|
||||||
|
parentId: "help",
|
||||||
|
id: "open-support",
|
||||||
|
orderNumber: 30,
|
||||||
|
label: "Support",
|
||||||
|
|
||||||
|
// TODO: Convert to async/await
|
||||||
|
onClick: () => {
|
||||||
|
openLinkInBrowser(supportUrl).catch((error) => {
|
||||||
|
logger.error("[MENU]: failed to open browser", { error });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default openSupportItemInjectable;
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token";
|
||||||
|
import navigateToExtensionsInjectable from "../../../../../../common/front-end-routing/routes/extensions/navigate-to-extensions.injectable";
|
||||||
|
import isMacInjectable from "../../../../../../common/vars/is-mac.injectable";
|
||||||
|
|
||||||
|
const navigateToExtensionsMenuItem = getInjectable({
|
||||||
|
id: "navigate-to-extensions-menu-item",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const navigateToExtensions = di.inject(navigateToExtensionsInjectable);
|
||||||
|
const isMac = di.inject(isMacInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: "clickable-menu-item" as const,
|
||||||
|
parentId: isMac ? "mac" : "file",
|
||||||
|
id: "navigate-to-extensions",
|
||||||
|
orderNumber: isMac ? 50 : 40,
|
||||||
|
label: "Extensions",
|
||||||
|
keyboardShortcut: isMac ? "CmdOrCtrl+Shift+E" : "Ctrl+Shift+E",
|
||||||
|
|
||||||
|
onClick: () => {
|
||||||
|
navigateToExtensions();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default navigateToExtensionsMenuItem;
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
getApplicationMenuOperationSystemActionInjectable,
|
||||||
|
} from "../../get-application-menu-operation-system-action-injectable";
|
||||||
|
|
||||||
|
export const actionForServices = getApplicationMenuOperationSystemActionInjectable({
|
||||||
|
id: "services",
|
||||||
|
parentId: "mac",
|
||||||
|
orderNumber: 80,
|
||||||
|
actionName: "services",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const actionForHide = getApplicationMenuOperationSystemActionInjectable({
|
||||||
|
id: "hide",
|
||||||
|
parentId: "mac",
|
||||||
|
orderNumber: 100,
|
||||||
|
actionName: "hide",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const actionForHideOthers = getApplicationMenuOperationSystemActionInjectable({
|
||||||
|
id: "hide-others",
|
||||||
|
parentId: "mac",
|
||||||
|
orderNumber: 110,
|
||||||
|
actionName: "hideOthers",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const actionForUnhide = getApplicationMenuOperationSystemActionInjectable({
|
||||||
|
id: "unhide",
|
||||||
|
parentId: "mac",
|
||||||
|
orderNumber: 120,
|
||||||
|
actionName: "unhide",
|
||||||
|
});
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../application-menu-item-injection-token";
|
||||||
|
import appNameInjectable from "../../../../../common/vars/app-name.injectable";
|
||||||
|
import isMacInjectable from "../../../../../common/vars/is-mac.injectable";
|
||||||
|
|
||||||
|
const primaryMenuItemInjectable = getInjectable({
|
||||||
|
id: "primary-application-menu-item",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const appName = di.inject(appNameInjectable);
|
||||||
|
const isMac = di.inject(isMacInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: "top-level-menu" as const,
|
||||||
|
parentId: "root" as const,
|
||||||
|
id: "mac",
|
||||||
|
orderNumber: 10,
|
||||||
|
label: appName,
|
||||||
|
isShown: isMac,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default primaryMenuItemInjectable;
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token";
|
||||||
|
import stopServicesAndExitAppInjectable from "../../../../../../main/stop-services-and-exit-app.injectable";
|
||||||
|
import isMacInjectable from "../../../../../../common/vars/is-mac.injectable";
|
||||||
|
|
||||||
|
const quitApplicationMenuItemInjectable = getInjectable({
|
||||||
|
id: "quit-application-menu-item",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const stopServicesAndExitApp = di.inject(stopServicesAndExitAppInjectable);
|
||||||
|
const isMac = di.inject(isMacInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: "clickable-menu-item" as const,
|
||||||
|
id: "quit",
|
||||||
|
label: "Quit",
|
||||||
|
|
||||||
|
parentId: isMac ? "mac" : "file",
|
||||||
|
orderNumber: isMac ? 140 : 70,
|
||||||
|
keyboardShortcut: isMac ? "Cmd+Q" : "Alt+F4",
|
||||||
|
|
||||||
|
onClick: () => {
|
||||||
|
stopServicesAndExitApp();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default quitApplicationMenuItemInjectable;
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
getApplicationMenuSeparatorInjectable,
|
||||||
|
} from "../../get-application-menu-separator-injectable";
|
||||||
|
|
||||||
|
export const separator1 = getApplicationMenuSeparatorInjectable({
|
||||||
|
id: "separator-1",
|
||||||
|
parentId: "mac",
|
||||||
|
orderNumber: 30,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const separator2 = getApplicationMenuSeparatorInjectable({
|
||||||
|
id: "separator-2",
|
||||||
|
parentId: "mac",
|
||||||
|
orderNumber: 70,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const separator3 = getApplicationMenuSeparatorInjectable({
|
||||||
|
id: "separator-3",
|
||||||
|
parentId: "mac",
|
||||||
|
orderNumber: 90,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const separator4 = getApplicationMenuSeparatorInjectable({
|
||||||
|
id: "separator-4",
|
||||||
|
parentId: "mac",
|
||||||
|
orderNumber: 130,
|
||||||
|
});
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import productNameInjectable from "../../../../../../common/vars/product-name.injectable";
|
||||||
|
import showAboutInjectable from "./show-about.injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token";
|
||||||
|
import isMacInjectable from "../../../../../../common/vars/is-mac.injectable";
|
||||||
|
|
||||||
|
const aboutMenuItemInjectable = getInjectable({
|
||||||
|
id: "about-menu-item",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const productName = di.inject(productNameInjectable);
|
||||||
|
const showAbout = di.inject(showAboutInjectable);
|
||||||
|
const isMac = di.inject(isMacInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: "clickable-menu-item" as const,
|
||||||
|
id: "about",
|
||||||
|
parentId: isMac ? "mac" : "help",
|
||||||
|
orderNumber: isMac ? 10 : 40,
|
||||||
|
label: `About ${productName}`,
|
||||||
|
|
||||||
|
onClick() {
|
||||||
|
showAbout();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default aboutMenuItemInjectable;
|
||||||
@ -3,13 +3,13 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import showMessagePopupInjectable from "../electron-app/features/show-message-popup.injectable";
|
import showMessagePopupInjectable from "../../../../../../main/electron-app/features/show-message-popup.injectable";
|
||||||
import isWindowsInjectable from "../../common/vars/is-windows.injectable";
|
import isWindowsInjectable from "../../../../../../common/vars/is-windows.injectable";
|
||||||
import appNameInjectable from "../../common/vars/app-name.injectable";
|
import appNameInjectable from "../../../../../../common/vars/app-name.injectable";
|
||||||
import productNameInjectable from "../../common/vars/product-name.injectable";
|
import productNameInjectable from "../../../../../../common/vars/product-name.injectable";
|
||||||
import buildVersionInjectable from "../vars/build-version/build-version.injectable";
|
import buildVersionInjectable from "../../../../../../main/vars/build-version/build-version.injectable";
|
||||||
import extensionApiVersionInjectable from "../../common/vars/extension-api-version.injectable";
|
import extensionApiVersionInjectable from "../../../../../../common/vars/extension-api-version.injectable";
|
||||||
import applicationCopyrightInjectable from "../../common/vars/application-copyright.injectable";
|
import applicationCopyrightInjectable from "../../../../../../common/vars/application-copyright.injectable";
|
||||||
|
|
||||||
const showAboutInjectable = getInjectable({
|
const showAboutInjectable = getInjectable({
|
||||||
id: "show-about",
|
id: "show-about",
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token";
|
||||||
|
import { webContents } from "electron";
|
||||||
|
|
||||||
|
const goBackMenuItemInjectable = getInjectable({
|
||||||
|
id: "go-back-menu-item",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
kind: "clickable-menu-item" as const,
|
||||||
|
parentId: "view",
|
||||||
|
id: "go-back",
|
||||||
|
orderNumber: 40,
|
||||||
|
label: "Back",
|
||||||
|
keyboardShortcut: "CmdOrCtrl+[",
|
||||||
|
|
||||||
|
onClick: () => {
|
||||||
|
webContents
|
||||||
|
.getAllWebContents()
|
||||||
|
.filter((wc) => wc.getType() === "window")
|
||||||
|
.forEach((wc) => wc.goBack());
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default goBackMenuItemInjectable;
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token";
|
||||||
|
import { webContents } from "electron";
|
||||||
|
|
||||||
|
const goForwardMenuItemInjectable = getInjectable({
|
||||||
|
id: "go-forward-menu-item",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
kind: "clickable-menu-item" as const,
|
||||||
|
parentId: "view",
|
||||||
|
id: "go-forward",
|
||||||
|
orderNumber: 50,
|
||||||
|
label: "Forward",
|
||||||
|
keyboardShortcut: "CmdOrCtrl+]",
|
||||||
|
|
||||||
|
onClick: () => {
|
||||||
|
webContents
|
||||||
|
.getAllWebContents()
|
||||||
|
.filter((wc) => wc.getType() === "window")
|
||||||
|
.forEach((wc) => wc.goForward());
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default goForwardMenuItemInjectable;
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token";
|
||||||
|
import navigateToCatalogInjectable from "../../../../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
||||||
|
|
||||||
|
const navigateToCatalogMenuItemInjectable = getInjectable({
|
||||||
|
id: "navigate-to-catalog-menu-item",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const navigateToCatalog = di.inject(navigateToCatalogInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: "clickable-menu-item" as const,
|
||||||
|
parentId: "view",
|
||||||
|
id: "navigate-to-catalog",
|
||||||
|
orderNumber: 10,
|
||||||
|
label: "Catalog",
|
||||||
|
keyboardShortcut: "Shift+CmdOrCtrl+C",
|
||||||
|
|
||||||
|
onClick: () => {
|
||||||
|
navigateToCatalog();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default navigateToCatalogMenuItemInjectable;
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token";
|
||||||
|
import broadcastMessageInjectable from "../../../../../../common/ipc/broadcast-message.injectable";
|
||||||
|
|
||||||
|
const openCommandPaletteMenuItemInjectable = getInjectable({
|
||||||
|
id: "open-command-palette-menu-item",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const broadcastMessage = di.inject(broadcastMessageInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: "clickable-menu-item" as const,
|
||||||
|
parentId: "view",
|
||||||
|
id: "open-command-palette",
|
||||||
|
orderNumber: 20,
|
||||||
|
label: "Command Palette...",
|
||||||
|
keyboardShortcut: "Shift+CmdOrCtrl+P",
|
||||||
|
|
||||||
|
onClick(_m, _b, event) {
|
||||||
|
/**
|
||||||
|
* Don't broadcast unless it was triggered by menu iteration so that
|
||||||
|
* there aren't double events in renderer
|
||||||
|
*
|
||||||
|
* NOTE: this `?` is required because of a bug in playwright. https://github.com/microsoft/playwright/issues/10554
|
||||||
|
*/
|
||||||
|
if (!event?.triggeredByAccelerator) {
|
||||||
|
broadcastMessage("command-palette:open");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default openCommandPaletteMenuItemInjectable;
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
getApplicationMenuOperationSystemActionInjectable,
|
||||||
|
} from "../../get-application-menu-operation-system-action-injectable";
|
||||||
|
|
||||||
|
export const actionForToggleDevTools = getApplicationMenuOperationSystemActionInjectable({
|
||||||
|
id: "toggle-dev-tools",
|
||||||
|
parentId: "view",
|
||||||
|
orderNumber: 70,
|
||||||
|
actionName: "toggleDevTools",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const actionForResetZoom = getApplicationMenuOperationSystemActionInjectable({
|
||||||
|
id: "reset-zoom",
|
||||||
|
parentId: "view",
|
||||||
|
orderNumber: 90,
|
||||||
|
actionName: "resetZoom",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const actionForZoomIn = getApplicationMenuOperationSystemActionInjectable({
|
||||||
|
id: "zoom-in",
|
||||||
|
parentId: "view",
|
||||||
|
orderNumber: 100,
|
||||||
|
actionName: "zoomIn",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const actionForZoomOut = getApplicationMenuOperationSystemActionInjectable({
|
||||||
|
id: "zoom-out",
|
||||||
|
parentId: "view",
|
||||||
|
orderNumber: 110,
|
||||||
|
actionName: "zoomOut",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const actionForToggleFullScreen = getApplicationMenuOperationSystemActionInjectable({
|
||||||
|
id: "toggle-full-screen",
|
||||||
|
parentId: "view",
|
||||||
|
orderNumber: 130,
|
||||||
|
actionName: "togglefullscreen",
|
||||||
|
});
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token";
|
||||||
|
import reloadCurrentApplicationWindowInjectable from "../../../../../../main/start-main-application/lens-window/reload-current-application-window.injectable";
|
||||||
|
|
||||||
|
const reloadMenuItemInjectable = getInjectable({
|
||||||
|
id: "reload-menu-item",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const reloadApplicationWindow = di.inject(
|
||||||
|
reloadCurrentApplicationWindowInjectable,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: "clickable-menu-item" as const,
|
||||||
|
parentId: "view",
|
||||||
|
id: "reload",
|
||||||
|
orderNumber: 60,
|
||||||
|
label: "Reload",
|
||||||
|
keyboardShortcut: "CmdOrCtrl+R",
|
||||||
|
|
||||||
|
onClick: () => {
|
||||||
|
reloadApplicationWindow();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default reloadMenuItemInjectable;
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
getApplicationMenuSeparatorInjectable,
|
||||||
|
} from "../../get-application-menu-separator-injectable";
|
||||||
|
|
||||||
|
export const separator1 = getApplicationMenuSeparatorInjectable({
|
||||||
|
id: "separator-1-for-view",
|
||||||
|
parentId: "view",
|
||||||
|
orderNumber: 30,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const separator2 = getApplicationMenuSeparatorInjectable({
|
||||||
|
id: "separator-2-for-view",
|
||||||
|
parentId: "view",
|
||||||
|
orderNumber: 80,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const separator3 = getApplicationMenuSeparatorInjectable({
|
||||||
|
id: "separator-3-for-view",
|
||||||
|
parentId: "view",
|
||||||
|
orderNumber: 120,
|
||||||
|
});
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import applicationMenuItemInjectionToken from "../application-menu-item-injection-token";
|
||||||
|
|
||||||
|
const viewMenuItemInjectable = getInjectable({
|
||||||
|
id: "view-application-menu-item",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
kind: "top-level-menu" as const,
|
||||||
|
parentId: "root" as const,
|
||||||
|
id: "view",
|
||||||
|
orderNumber: 40,
|
||||||
|
label: "View",
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default viewMenuItemInjectable;
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import populateApplicationMenuInjectable from "./populate-application-menu.injectable";
|
||||||
|
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
|
||||||
|
|
||||||
|
export default getGlobalOverride(populateApplicationMenuInjectable, () => () => {});
|
||||||
@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { Menu } from "electron";
|
||||||
|
import type { MenuItemOpts } from "./application-menu-items.injectable";
|
||||||
|
import type { Composite } from "../../../common/utils/composite/get-composite/get-composite";
|
||||||
|
import type { ApplicationMenuItemTypes } from "./menu-items/application-menu-item-injection-token";
|
||||||
|
import { pipeline } from "@ogre-tools/fp";
|
||||||
|
import { map, sortBy } from "lodash/fp";
|
||||||
|
import type { MenuItemRoot } from "./application-menu-item-composite.injectable";
|
||||||
|
import { checkThatAllDiscriminablesAreExhausted } from "../../../common/utils/composable-responsibilities/discriminable/discriminable";
|
||||||
|
|
||||||
|
const populateApplicationMenuInjectable = getInjectable({
|
||||||
|
id: "populate-application-menu",
|
||||||
|
|
||||||
|
instantiate:
|
||||||
|
() => (composite: Composite<ApplicationMenuItemTypes | MenuItemRoot>) => {
|
||||||
|
const electronTemplate = getApplicationMenuTemplate(composite);
|
||||||
|
const menu = Menu.buildFromTemplate(electronTemplate);
|
||||||
|
|
||||||
|
Menu.setApplicationMenu(menu);
|
||||||
|
},
|
||||||
|
|
||||||
|
causesSideEffects: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default populateApplicationMenuInjectable;
|
||||||
|
|
||||||
|
export const getApplicationMenuTemplate = (composite: Composite<ApplicationMenuItemTypes | MenuItemRoot>) => {
|
||||||
|
const topLevelMenus = composite.children.filter(
|
||||||
|
(x): x is Composite<ApplicationMenuItemTypes> => x.value.kind !== "root",
|
||||||
|
);
|
||||||
|
|
||||||
|
return topLevelMenus.map(toHierarchicalElectronMenuItem);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toHierarchicalElectronMenuItem = (
|
||||||
|
composite: Composite<ApplicationMenuItemTypes>,
|
||||||
|
): MenuItemOpts => {
|
||||||
|
const value = composite.value;
|
||||||
|
|
||||||
|
switch (value.kind) {
|
||||||
|
case "top-level-menu": {
|
||||||
|
const { id } = composite;
|
||||||
|
const { label, role } = value;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...(id ? { id } : {}),
|
||||||
|
...(role ? { role } : {}),
|
||||||
|
label,
|
||||||
|
|
||||||
|
submenu: pipeline(
|
||||||
|
composite.children,
|
||||||
|
sortBy((childComposite) => childComposite.value.orderNumber),
|
||||||
|
map(toHierarchicalElectronMenuItem),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "sub-menu": {
|
||||||
|
const { id } = composite;
|
||||||
|
const { label } = value;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...(id ? { id } : {}),
|
||||||
|
label,
|
||||||
|
|
||||||
|
submenu: pipeline(
|
||||||
|
composite.children,
|
||||||
|
sortBy((childComposite) => childComposite.value.orderNumber),
|
||||||
|
map(toHierarchicalElectronMenuItem),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "clickable-menu-item": {
|
||||||
|
const { id } = composite;
|
||||||
|
const { label, onClick, keyboardShortcut } = value;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...(id ? { id } : {}),
|
||||||
|
...(label ? { label } : {}),
|
||||||
|
...(keyboardShortcut ? { accelerator: keyboardShortcut }: {}),
|
||||||
|
click: onClick,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "os-action-menu-item": {
|
||||||
|
const { label, keyboardShortcut, actionName } = value;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...(label ? { label } : {}),
|
||||||
|
...(keyboardShortcut ? { accelerator: keyboardShortcut } : {}),
|
||||||
|
role: actionName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "separator": {
|
||||||
|
return {
|
||||||
|
type: "separator",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
throw checkThatAllDiscriminablesAreExhausted(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -3,15 +3,15 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import applicationMenuInjectable from "./application-menu.injectable";
|
import applicationMenuReactivityInjectable from "./application-menu-reactivity.injectable";
|
||||||
import { onLoadOfApplicationInjectionToken } from "../start-main-application/runnable-tokens/on-load-of-application-injection-token";
|
import { onLoadOfApplicationInjectionToken } from "../../../main/start-main-application/runnable-tokens/on-load-of-application-injection-token";
|
||||||
|
|
||||||
const startApplicationMenuInjectable = getInjectable({
|
const startApplicationMenuInjectable = getInjectable({
|
||||||
id: "start-application-menu",
|
id: "start-application-menu",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const applicationMenu = di.inject(
|
const applicationMenu = di.inject(
|
||||||
applicationMenuInjectable,
|
applicationMenuReactivityInjectable,
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -3,15 +3,15 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import applicationMenuInjectable from "./application-menu.injectable";
|
import applicationMenuReactivityInjectable from "./application-menu-reactivity.injectable";
|
||||||
import { beforeQuitOfBackEndInjectionToken } from "../start-main-application/runnable-tokens/before-quit-of-back-end-injection-token";
|
import { beforeQuitOfBackEndInjectionToken } from "../../../main/start-main-application/runnable-tokens/before-quit-of-back-end-injection-token";
|
||||||
|
|
||||||
const stopApplicationMenuInjectable = getInjectable({
|
const stopApplicationMenuInjectable = getInjectable({
|
||||||
id: "stop-application-menu",
|
id: "stop-application-menu",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const applicationMenu = di.inject(
|
const applicationMenu = di.inject(
|
||||||
applicationMenuInjectable,
|
applicationMenuReactivityInjectable,
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -12,43 +12,61 @@ exports[`installing update when started renders 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive disabled focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="home"
|
data-testid="home-button"
|
||||||
>
|
>
|
||||||
home
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="home"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
home
|
||||||
data-testid="history-back"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_back"
|
data-testid="history-back"
|
||||||
>
|
>
|
||||||
arrow_back
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_back"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
arrow_back
|
||||||
data-testid="history-forward"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_forward"
|
data-testid="history-forward"
|
||||||
>
|
>
|
||||||
arrow_forward
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
@ -214,43 +232,61 @@ exports[`installing update when started when user checks for updates renders 1`]
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive disabled focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="home"
|
data-testid="home-button"
|
||||||
>
|
>
|
||||||
home
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="home"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
home
|
||||||
data-testid="history-back"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_back"
|
data-testid="history-back"
|
||||||
>
|
>
|
||||||
arrow_back
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_back"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
arrow_back
|
||||||
data-testid="history-forward"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_forward"
|
data-testid="history-forward"
|
||||||
>
|
>
|
||||||
arrow_forward
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
@ -416,43 +452,61 @@ exports[`installing update when started when user checks for updates when new up
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive disabled focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="home"
|
data-testid="home-button"
|
||||||
>
|
>
|
||||||
home
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="home"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
home
|
||||||
data-testid="history-back"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_back"
|
data-testid="history-back"
|
||||||
>
|
>
|
||||||
arrow_back
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_back"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
arrow_back
|
||||||
data-testid="history-forward"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_forward"
|
data-testid="history-forward"
|
||||||
>
|
>
|
||||||
arrow_forward
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
@ -618,61 +672,83 @@ exports[`installing update when started when user checks for updates when new up
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive disabled focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
>
|
>
|
||||||
<span
|
|
||||||
class="icon"
|
|
||||||
data-icon-name="home"
|
|
||||||
>
|
|
||||||
home
|
|
||||||
</span>
|
|
||||||
</i>
|
|
||||||
<i
|
|
||||||
class="Icon material interactive disabled focusable"
|
|
||||||
data-testid="history-back"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="icon"
|
|
||||||
data-icon-name="arrow_back"
|
|
||||||
>
|
|
||||||
arrow_back
|
|
||||||
</span>
|
|
||||||
</i>
|
|
||||||
<i
|
|
||||||
class="Icon material interactive disabled focusable"
|
|
||||||
data-testid="history-forward"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="icon"
|
|
||||||
data-icon-name="arrow_forward"
|
|
||||||
>
|
|
||||||
arrow_forward
|
|
||||||
</span>
|
|
||||||
</i>
|
|
||||||
<button
|
|
||||||
class="updateButton"
|
|
||||||
data-testid="update-button"
|
|
||||||
data-warning-level="light"
|
|
||||||
id="update-lens-button"
|
|
||||||
>
|
|
||||||
Update
|
|
||||||
<i
|
<i
|
||||||
class="Icon icon material focusable"
|
class="Icon material interactive disabled focusable"
|
||||||
|
data-testid="home-button"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="icon"
|
class="icon"
|
||||||
data-icon-name="arrow_drop_down"
|
data-icon-name="home"
|
||||||
>
|
>
|
||||||
arrow_drop_down
|
home
|
||||||
</span>
|
</span>
|
||||||
</i>
|
</i>
|
||||||
</button>
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon material interactive disabled focusable"
|
||||||
|
data-testid="history-back"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="arrow_back"
|
||||||
|
>
|
||||||
|
arrow_back
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon material interactive disabled focusable"
|
||||||
|
data-testid="history-forward"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="updateButton"
|
||||||
|
data-testid="update-button"
|
||||||
|
data-warning-level="light"
|
||||||
|
id="update-lens-button"
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
<i
|
||||||
|
class="Icon icon material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="arrow_drop_down"
|
||||||
|
>
|
||||||
|
arrow_drop_down
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
@ -838,61 +914,83 @@ exports[`installing update when started when user checks for updates when new up
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive disabled focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
>
|
>
|
||||||
<span
|
|
||||||
class="icon"
|
|
||||||
data-icon-name="home"
|
|
||||||
>
|
|
||||||
home
|
|
||||||
</span>
|
|
||||||
</i>
|
|
||||||
<i
|
|
||||||
class="Icon material interactive disabled focusable"
|
|
||||||
data-testid="history-back"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="icon"
|
|
||||||
data-icon-name="arrow_back"
|
|
||||||
>
|
|
||||||
arrow_back
|
|
||||||
</span>
|
|
||||||
</i>
|
|
||||||
<i
|
|
||||||
class="Icon material interactive disabled focusable"
|
|
||||||
data-testid="history-forward"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="icon"
|
|
||||||
data-icon-name="arrow_forward"
|
|
||||||
>
|
|
||||||
arrow_forward
|
|
||||||
</span>
|
|
||||||
</i>
|
|
||||||
<button
|
|
||||||
class="updateButton"
|
|
||||||
data-testid="update-button"
|
|
||||||
data-warning-level="light"
|
|
||||||
id="update-lens-button"
|
|
||||||
>
|
|
||||||
Update
|
|
||||||
<i
|
<i
|
||||||
class="Icon icon material focusable"
|
class="Icon material interactive disabled focusable"
|
||||||
|
data-testid="home-button"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="icon"
|
class="icon"
|
||||||
data-icon-name="arrow_drop_down"
|
data-icon-name="home"
|
||||||
>
|
>
|
||||||
arrow_drop_down
|
home
|
||||||
</span>
|
</span>
|
||||||
</i>
|
</i>
|
||||||
</button>
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon material interactive disabled focusable"
|
||||||
|
data-testid="history-back"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="arrow_back"
|
||||||
|
>
|
||||||
|
arrow_back
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon material interactive disabled focusable"
|
||||||
|
data-testid="history-forward"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="updateButton"
|
||||||
|
data-testid="update-button"
|
||||||
|
data-warning-level="light"
|
||||||
|
id="update-lens-button"
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
<i
|
||||||
|
class="Icon icon material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="arrow_drop_down"
|
||||||
|
>
|
||||||
|
arrow_drop_down
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
@ -1058,43 +1156,61 @@ exports[`installing update when started when user checks for updates when no new
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive disabled focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="home"
|
data-testid="home-button"
|
||||||
>
|
>
|
||||||
home
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="home"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
home
|
||||||
data-testid="history-back"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_back"
|
data-testid="history-back"
|
||||||
>
|
>
|
||||||
arrow_back
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_back"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
arrow_back
|
||||||
data-testid="history-forward"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_forward"
|
data-testid="history-forward"
|
||||||
>
|
>
|
||||||
arrow_forward
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -5,18 +5,18 @@
|
|||||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
import electronUpdaterIsActiveInjectable from "../../main/electron-app/features/electron-updater-is-active.injectable";
|
import electronUpdaterIsActiveInjectable from "../../main/electron-app/features/electron-updater-is-active.injectable";
|
||||||
import publishIsConfiguredInjectable from "../../main/application-update/publish-is-configured.injectable";
|
import publishIsConfiguredInjectable from "./main/updating-is-enabled/publish-is-configured/publish-is-configured.injectable";
|
||||||
import type { AsyncFnMock } from "@async-fn/jest";
|
import type { AsyncFnMock } from "@async-fn/jest";
|
||||||
import asyncFn from "@async-fn/jest";
|
import asyncFn from "@async-fn/jest";
|
||||||
import type { CheckForPlatformUpdates } from "../../main/application-update/check-for-platform-updates/check-for-platform-updates.injectable";
|
import type { CheckForPlatformUpdates } from "./main/check-for-updates/check-for-platform-updates/check-for-platform-updates.injectable";
|
||||||
import checkForPlatformUpdatesInjectable from "../../main/application-update/check-for-platform-updates/check-for-platform-updates.injectable";
|
import checkForPlatformUpdatesInjectable from "./main/check-for-updates/check-for-platform-updates/check-for-platform-updates.injectable";
|
||||||
import appEventBusInjectable from "../../common/app-event-bus/app-event-bus.injectable";
|
import appEventBusInjectable from "../../common/app-event-bus/app-event-bus.injectable";
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
import processCheckingForUpdatesInjectable from "../../main/application-update/check-for-updates/process-checking-for-updates.injectable";
|
import processCheckingForUpdatesInjectable from "./main/process-checking-for-updates.injectable";
|
||||||
import type { DownloadPlatformUpdate } from "../../main/application-update/download-platform-update/download-platform-update.injectable";
|
import type { DownloadPlatformUpdate } from "./main/download-update/download-platform-update/download-platform-update.injectable";
|
||||||
import downloadPlatformUpdateInjectable from "../../main/application-update/download-platform-update/download-platform-update.injectable";
|
import downloadPlatformUpdateInjectable from "./main/download-update/download-platform-update/download-platform-update.injectable";
|
||||||
import quitAndInstallUpdateInjectable from "../../main/application-update/quit-and-install-update.injectable";
|
import quitAndInstallUpdateInjectable from "./main/quit-and-install-update.injectable";
|
||||||
import periodicalCheckForUpdatesInjectable from "../../main/application-update/periodical-check-for-updates/periodical-check-for-updates.injectable";
|
import periodicalCheckForUpdatesInjectable from "./child-features/periodical-checking-of-updates/main/periodical-check-for-updates.injectable";
|
||||||
import { advanceFakeTime, useFakeTime } from "../../common/test-utils/use-fake-time";
|
import { advanceFakeTime, useFakeTime } from "../../common/test-utils/use-fake-time";
|
||||||
import emitEventInjectable from "../../common/app-event-bus/emit-event.injectable";
|
import emitEventInjectable from "../../common/app-event-bus/emit-event.injectable";
|
||||||
import getBuildVersionInjectable from "../../main/vars/build-version/get-build-version.injectable";
|
import getBuildVersionInjectable from "../../main/vars/build-version/get-build-version.injectable";
|
||||||
@ -142,7 +142,7 @@ describe("analytics for installing update", () => {
|
|||||||
it("when checking for updates using application menu, sends event to analytics for being checked from application menu", async () => {
|
it("when checking for updates using application menu, sends event to analytics for being checked from application menu", async () => {
|
||||||
analyticsListenerMock.mockClear();
|
analyticsListenerMock.mockClear();
|
||||||
|
|
||||||
builder.applicationMenu.click("root.check-for-updates");
|
builder.applicationMenu.click("root", "mac", "check-for-updates");
|
||||||
|
|
||||||
expect(analyticsListenerMock.mock.calls).toEqual([
|
expect(analyticsListenerMock.mock.calls).toEqual([
|
||||||
[
|
[
|
||||||
|
|||||||
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
import applicationMenuItemInjectionToken from "../../../../application-menu/main/menu-items/application-menu-item-injection-token";
|
||||||
|
import processCheckingForUpdatesInjectable from "../../../main/process-checking-for-updates.injectable";
|
||||||
|
import showApplicationWindowInjectable from "../../../../../main/start-main-application/lens-window/show-application-window.injectable";
|
||||||
|
import updatingIsEnabledInjectable from "../../../main/updating-is-enabled/updating-is-enabled.injectable";
|
||||||
|
import isMacInjectable from "../../../../../common/vars/is-mac.injectable";
|
||||||
|
|
||||||
|
const checkForUpdatesMenuItemInjectable = getInjectable({
|
||||||
|
id: "check-for-updates-menu-item",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const processCheckingForUpdates = di.inject(
|
||||||
|
processCheckingForUpdatesInjectable,
|
||||||
|
);
|
||||||
|
|
||||||
|
const showApplicationWindow = di.inject(showApplicationWindowInjectable);
|
||||||
|
|
||||||
|
const updatingIsEnabled = di.inject(updatingIsEnabledInjectable);
|
||||||
|
const isMac = di.inject(isMacInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: "clickable-menu-item" as const,
|
||||||
|
id: "check-for-updates",
|
||||||
|
parentId: isMac ? "mac" : "help",
|
||||||
|
orderNumber: isMac ? 20 : 50,
|
||||||
|
label: "Check for updates",
|
||||||
|
isShown: updatingIsEnabled,
|
||||||
|
|
||||||
|
onClick: () => {
|
||||||
|
// Todo: implement using async/await
|
||||||
|
processCheckingForUpdates("application-menu").then(() =>
|
||||||
|
showApplicationWindow(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: applicationMenuItemInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default checkForUpdatesMenuItemInjectable;
|
||||||
@ -12,61 +12,83 @@ exports[`encourage user to update when sufficient time passed since update was d
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive disabled focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
>
|
>
|
||||||
<span
|
|
||||||
class="icon"
|
|
||||||
data-icon-name="home"
|
|
||||||
>
|
|
||||||
home
|
|
||||||
</span>
|
|
||||||
</i>
|
|
||||||
<i
|
|
||||||
class="Icon material interactive disabled focusable"
|
|
||||||
data-testid="history-back"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="icon"
|
|
||||||
data-icon-name="arrow_back"
|
|
||||||
>
|
|
||||||
arrow_back
|
|
||||||
</span>
|
|
||||||
</i>
|
|
||||||
<i
|
|
||||||
class="Icon material interactive disabled focusable"
|
|
||||||
data-testid="history-forward"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="icon"
|
|
||||||
data-icon-name="arrow_forward"
|
|
||||||
>
|
|
||||||
arrow_forward
|
|
||||||
</span>
|
|
||||||
</i>
|
|
||||||
<button
|
|
||||||
class="updateButton"
|
|
||||||
data-testid="update-button"
|
|
||||||
data-warning-level="light"
|
|
||||||
id="update-lens-button"
|
|
||||||
>
|
|
||||||
Update
|
|
||||||
<i
|
<i
|
||||||
class="Icon icon material focusable"
|
class="Icon material interactive disabled focusable"
|
||||||
|
data-testid="home-button"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="icon"
|
class="icon"
|
||||||
data-icon-name="arrow_drop_down"
|
data-icon-name="home"
|
||||||
>
|
>
|
||||||
arrow_drop_down
|
home
|
||||||
</span>
|
</span>
|
||||||
</i>
|
</i>
|
||||||
</button>
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon material interactive disabled focusable"
|
||||||
|
data-testid="history-back"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="arrow_back"
|
||||||
|
>
|
||||||
|
arrow_back
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon material interactive disabled focusable"
|
||||||
|
data-testid="history-forward"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="updateButton"
|
||||||
|
data-testid="update-button"
|
||||||
|
data-warning-level="light"
|
||||||
|
id="update-lens-button"
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
<i
|
||||||
|
class="Icon icon material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="arrow_drop_down"
|
||||||
|
>
|
||||||
|
arrow_drop_down
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
@ -232,43 +254,61 @@ exports[`encourage user to update when sufficient time passed since update was d
|
|||||||
<div
|
<div
|
||||||
class="items"
|
class="items"
|
||||||
>
|
>
|
||||||
<i
|
<div
|
||||||
class="Icon material interactive disabled focusable"
|
class="preventedDragging"
|
||||||
data-testid="home-button"
|
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="home"
|
data-testid="home-button"
|
||||||
>
|
>
|
||||||
home
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="home"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
home
|
||||||
data-testid="history-back"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_back"
|
data-testid="history-back"
|
||||||
>
|
>
|
||||||
arrow_back
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_back"
|
||||||
<i
|
>
|
||||||
class="Icon material interactive disabled focusable"
|
arrow_back
|
||||||
data-testid="history-forward"
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
>
|
>
|
||||||
<span
|
<i
|
||||||
class="icon"
|
class="Icon material interactive disabled focusable"
|
||||||
data-icon-name="arrow_forward"
|
data-testid="history-forward"
|
||||||
>
|
>
|
||||||
arrow_forward
|
<span
|
||||||
</span>
|
class="icon"
|
||||||
</i>
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="items"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
@ -6,17 +6,17 @@ import type { AsyncFnMock } from "@async-fn/jest";
|
|||||||
import asyncFn from "@async-fn/jest";
|
import asyncFn from "@async-fn/jest";
|
||||||
import type { RenderResult } from "@testing-library/react";
|
import type { RenderResult } from "@testing-library/react";
|
||||||
import { act } from "@testing-library/react";
|
import { act } from "@testing-library/react";
|
||||||
import type { CheckForPlatformUpdates } from "../../main/application-update/check-for-platform-updates/check-for-platform-updates.injectable";
|
import type { CheckForPlatformUpdates } from "../../main/check-for-updates/check-for-platform-updates/check-for-platform-updates.injectable";
|
||||||
import checkForPlatformUpdatesInjectable from "../../main/application-update/check-for-platform-updates/check-for-platform-updates.injectable";
|
import checkForPlatformUpdatesInjectable from "../../main/check-for-updates/check-for-platform-updates/check-for-platform-updates.injectable";
|
||||||
import type { DownloadPlatformUpdate } from "../../main/application-update/download-platform-update/download-platform-update.injectable";
|
import type { DownloadPlatformUpdate } from "../../main/download-update/download-platform-update/download-platform-update.injectable";
|
||||||
import downloadPlatformUpdateInjectable from "../../main/application-update/download-platform-update/download-platform-update.injectable";
|
import downloadPlatformUpdateInjectable from "../../main/download-update/download-platform-update/download-platform-update.injectable";
|
||||||
import publishIsConfiguredInjectable from "../../main/application-update/publish-is-configured.injectable";
|
import publishIsConfiguredInjectable from "../../main/updating-is-enabled/publish-is-configured/publish-is-configured.injectable";
|
||||||
import electronUpdaterIsActiveInjectable from "../../main/electron-app/features/electron-updater-is-active.injectable";
|
import electronUpdaterIsActiveInjectable from "../../../../main/electron-app/features/electron-updater-is-active.injectable";
|
||||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
import type { ApplicationBuilder } from "../../../../renderer/components/test-utils/get-application-builder";
|
||||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
import { getApplicationBuilder } from "../../../../renderer/components/test-utils/get-application-builder";
|
||||||
import processCheckingForUpdatesInjectable from "../../main/application-update/check-for-updates/process-checking-for-updates.injectable";
|
import processCheckingForUpdatesInjectable from "../../main/process-checking-for-updates.injectable";
|
||||||
import quitAndInstallUpdateInjectable from "../../main/application-update/quit-and-install-update.injectable";
|
import quitAndInstallUpdateInjectable from "../../main/quit-and-install-update.injectable";
|
||||||
import { advanceFakeTime, useFakeTime } from "../../common/test-utils/use-fake-time";
|
import { advanceFakeTime, useFakeTime } from "../../../../common/test-utils/use-fake-time";
|
||||||
|
|
||||||
function daysToMilliseconds(days: number) {
|
function daysToMilliseconds(days: number) {
|
||||||
return Math.round(days * 24 * 60 * 60 * 1000);
|
return Math.round(days * 24 * 60 * 60 * 1000);
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import { topBarItemOnRightSideInjectionToken } from "../../../../../../renderer/components/layout/top-bar/top-bar-items/top-bar-item-injection-token";
|
||||||
|
import { UpdateButton } from "./update-button";
|
||||||
|
import updateWarningLevelInjectable from "./update-warning-level.injectable";
|
||||||
|
|
||||||
|
const updateApplicationTopBarItemInjectable = getInjectable({
|
||||||
|
id: "update-application-top-bar-item",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const warningLevel = di.inject(updateWarningLevelInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: "update-application",
|
||||||
|
isShown: computed(() => !!warningLevel.get()),
|
||||||
|
orderNumber: 50,
|
||||||
|
Component: UpdateButton,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: topBarItemOnRightSideInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default updateApplicationTopBarItemInjectable;
|
||||||
@ -7,15 +7,15 @@ import styles from "./styles.module.scss";
|
|||||||
|
|
||||||
import type { HTMLAttributes } from "react";
|
import type { HTMLAttributes } from "react";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Menu, MenuItem } from "../menu";
|
import { Menu, MenuItem } from "../../../../../../../renderer/components/menu";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../../../../../../renderer/utils";
|
||||||
import type { IconProps } from "../icon";
|
import type { IconProps } from "../../../../../../../renderer/components/icon";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../../../../../../../renderer/components/icon";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { IComputedValue } from "mobx";
|
import type { IComputedValue } from "mobx";
|
||||||
import restartAndInstallUpdateInjectable from "./restart-and-install-update.injectable";
|
import restartAndInstallUpdateInjectable from "../../../../../renderer/restart-and-install-update.injectable";
|
||||||
import updateWarningLevelInjectable from "./update-warning-level.injectable";
|
import updateWarningLevelInjectable from "../update-warning-level.injectable";
|
||||||
|
|
||||||
interface UpdateButtonProps extends HTMLAttributes<HTMLButtonElement> {}
|
interface UpdateButtonProps extends HTMLAttributes<HTMLButtonElement> {}
|
||||||
|
|
||||||
@ -34,10 +34,6 @@ export const NonInjectedUpdateButton = observer(({ warningLevel, update, id }: U
|
|||||||
setOpened(!opened);
|
setOpened(!opened);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!level) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user