Hello community,

here is the log from the commit of package yast2-packager for openSUSE:Factory 
checked in at 2020-03-22 14:16:13
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/yast2-packager (Old)
 and      /work/SRC/openSUSE:Factory/.yast2-packager.new.3160 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "yast2-packager"

Sun Mar 22 14:16:13 2020 rev:390 rq:786195 version:4.2.59

Changes:
--------
--- /work/SRC/openSUSE:Factory/yast2-packager/yast2-packager.changes    
2020-03-14 09:54:39.823080334 +0100
+++ /work/SRC/openSUSE:Factory/.yast2-packager.new.3160/yast2-packager.changes  
2020-03-22 14:16:16.666022457 +0100
@@ -1,0 +2,6 @@
+Thu Mar 12 23:24:18 UTC 2020 - David Diaz <dgonza...@suse.com>
+
+- Improve the product selection dialog (related to bsc#1157780).
+- 4.2.59
+
+-------------------------------------------------------------------

Old:
----
  yast2-packager-4.2.58.tar.bz2

New:
----
  yast2-packager-4.2.59.tar.bz2

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ yast2-packager.spec ++++++
--- /var/tmp/diff_new_pack.BAD8Sv/_old  2020-03-22 14:16:17.366022906 +0100
+++ /var/tmp/diff_new_pack.BAD8Sv/_new  2020-03-22 14:16:17.382022917 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           yast2-packager
-Version:        4.2.58
+Version:        4.2.59
 Release:        0
 Summary:        YaST2 - Package Library
 License:        GPL-2.0-or-later
@@ -35,8 +35,8 @@
 BuildRequires:  yast2-storage-ng >= 4.0.141
 # break the yast2-packager -> yast2-storage-ng -> yast2-packager build cycle
 #!BuildIgnore: yast2-packager
-# Y2Packager::Repositories
-BuildRequires:  yast2 >= 4.2.60
+# CWM::MultiStatusSelector
+BuildRequires:  yast2 >= 4.2.72
 # Pkg::Resolvables
 BuildRequires:  yast2-pkg-bindings >= 4.2.0
 # Augeas lenses
@@ -47,8 +47,8 @@
 Requires:       yast2-country-data >= 2.16.3
 # Pkg::Resolvables
 Requires:       yast2-pkg-bindings >= 4.2.0
-# Y2Packager::Repositories
-Requires:       yast2 >= 4.2.60
+# CWM::MultiStatusSelector
+Requires:       yast2 >= 4.2.72
 # unzipping license file
 Requires:       unzip
 # HTTP, FTP, HTTPS modules (inst_productsources.ycp)

++++++ yast2-packager-4.2.58.tar.bz2 -> yast2-packager-4.2.59.tar.bz2 ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yast2-packager-4.2.58/package/yast2-packager.changes 
new/yast2-packager-4.2.59/package/yast2-packager.changes
--- old/yast2-packager-4.2.58/package/yast2-packager.changes    2020-03-06 
13:22:46.000000000 +0100
+++ new/yast2-packager-4.2.59/package/yast2-packager.changes    2020-03-18 
15:04:45.000000000 +0100
@@ -1,4 +1,10 @@
 -------------------------------------------------------------------
+Thu Mar 12 23:24:18 UTC 2020 - David Diaz <dgonza...@suse.com>
+
+- Improve the product selection dialog (related to bsc#1157780).
+- 4.2.59
+
+-------------------------------------------------------------------
 Fri Mar  6 09:11:15 UTC 2020 - Ladislav Slezák <lsle...@suse.cz>
 
 - Avoid unselecting the base product (related to bsc#1165501)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yast2-packager-4.2.58/package/yast2-packager.spec 
new/yast2-packager-4.2.59/package/yast2-packager.spec
--- old/yast2-packager-4.2.58/package/yast2-packager.spec       2020-03-06 
13:22:46.000000000 +0100
+++ new/yast2-packager-4.2.59/package/yast2-packager.spec       2020-03-18 
15:04:45.000000000 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           yast2-packager
-Version:        4.2.58
+Version:        4.2.59
 Release:        0
 Summary:        YaST2 - Package Library
 License:        GPL-2.0-or-later
@@ -35,8 +35,8 @@
 BuildRequires:  yast2-storage-ng >= 4.0.141
 # break the yast2-packager -> yast2-storage-ng -> yast2-packager build cycle
 #!BuildIgnore: yast2-packager
-# Y2Packager::Repositories
-BuildRequires:  yast2 >= 4.2.60
+# CWM::MultiStatusSelector
+BuildRequires:  yast2 >= 4.2.72
 # Pkg::Resolvables
 BuildRequires:  yast2-pkg-bindings >= 4.2.0
 # Augeas lenses
@@ -47,8 +47,8 @@
 Requires:       yast2-country-data >= 2.16.3
 # Pkg::Resolvables
 Requires:       yast2-pkg-bindings >= 4.2.0
-# Y2Packager::Repositories
-Requires:       yast2 >= 4.2.60
+# CWM::MultiStatusSelector
+Requires:       yast2 >= 4.2.72
 # unzipping license file
 Requires:       unzip
 # HTTP, FTP, HTTPS modules (inst_productsources.ycp)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-packager-4.2.58/src/lib/y2packager/dialogs/addon_selector.rb 
new/yast2-packager-4.2.59/src/lib/y2packager/dialogs/addon_selector.rb
--- old/yast2-packager-4.2.58/src/lib/y2packager/dialogs/addon_selector.rb      
2020-03-06 13:22:46.000000000 +0100
+++ new/yast2-packager-4.2.59/src/lib/y2packager/dialogs/addon_selector.rb      
2020-03-18 15:04:45.000000000 +0100
@@ -1,37 +1,40 @@
-# 
------------------------------------------------------------------------------
-# Copyright (c) 2017 SUSE LLC, All Rights Reserved.
+# Copyright (c) [2017-2020] SUSE LLC
 #
-# This program is free software; you can redistribute it and/or modify it under
-# the terms of version 2 of the GNU General Public License as published by the
-# Free Software Foundation.
+# All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of version 2 of the GNU General Public License as published
+# by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 
details.
-# 
------------------------------------------------------------------------------
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, contact SUSE LLC.
+#
+# To contact SUSE LLC about this file by physical or electronic mail, you may
+# find current contact information at www.suse.com.
 
 require "yast"
-require "erb"
-require "ui/installation_dialog"
+require "cwm/dialog"
 require "y2packager/resolvable"
+require "y2packager/widgets/addons_selector"
 
 Yast.import "AddOnProduct"
 Yast.import "Mode"
 Yast.import "ProductFeatures"
-Yast.import "Report"
 Yast.import "Stage"
-Yast.import "UI"
 Yast.import "Wizard"
 
 module Y2Packager
   module Dialogs
     # Dialog which shows the user available products on the medium
-    class AddonSelector < ::UI::InstallationDialog
-      include Yast::Logger
-      include ERB::Util
-
+    class AddonSelector < ::CWM::Dialog
       # @return [Array<Y2Packager::ProductLocation>] Products on the medium
       attr_reader :products
+
       # @return [Array<Y2Packager::ProductLocation>] User selected products
       attr_reader :selected_products
 
@@ -43,156 +46,107 @@
       #
       # @param products [Array<Y2Packager::ProductLocation>] Products on the 
medium
       def initialize(products)
-        super()
         textdomain "packager"
 
         @products = products
-        # do not offer base products, they would conflict with the already 
selected base product,
-        # allow a hidden way to force displaying them in some special cases
+        # do not offer base products, they would conflict with the already
+        # selected base product, allow a hidden way to force displaying them in
+        # some special cases
         @products.reject! { |p| p.details&.base } if 
ENV["Y2_DISPLAY_BASE_PRODUCTS"] != "1"
         @selected_products = []
       end
 
-      # Handler for the :next action
+      # The dialog entry point
       #
-      # This action happens when the user clicks the 'Next' button
-      def next_handler
-        read_user_selection
-
-        return if selected_products.empty? && 
!Yast::Popup.ContinueCancel(continue_msg)
-
-        finish_dialog(:next)
-      end
-
-      # Handler for the :abort action
-      # Confirm abort when running in the initial stage (inst-sys)
-      def abort_handler
-        return if Yast::Stage.initial && !Yast::Popup.ConfirmAbort(:painless)
-
-        finish_dialog(:abort)
-      end
-
-      # Text to display when the help button is pressed
+      # Display the dialog title on the left side at installation (in the first
+      # stage) to have the same layout as in the registration addons dialog.
       #
-      # @return [String]
-      def help_text
-        # TRANSLATORS: help text
-        _("<p>The selected repository contains several products in independent 
" \
-        "subdirectories. Select which products you want to install.</p>")
-      end
-
-      # Handle changing the current item or changing the selection
-      def addon_repos_handler
-        current_product = find_current_product
-        return unless current_product
-
-        refresh_details(current_product)
-
-        select_dependent_products
-      end
-
-      # Display the the dialog title on the left side at installation
-      # (in the first stage) to have the same layout as in the registration
-      # addons dialog.
+      # @see CWM::Dialog#run
       def run
         Yast::Wizard.OpenLeftTitleNextBackDialog if Yast::Stage.initial
-        super()
+        super
       ensure
         Yast::Wizard.CloseDialog if Yast::Stage.initial
       end
 
-      # overwrite dialog creation to always enable back/next by default
-      def create_dialog
-        res = super
-        Yast::Wizard.EnableNextButton
-        Yast::Wizard.EnableBackButton
-        Yast::UI.SetFocus(Id(:addon_repos))
-        res
-      end
-
-    private
-
-      attr_writer :selected_products
-
-      def selection_content
-        defaults = preselected_products
-        products.map { |p| Item(Id(p.dir), p.summary || p.name, 
defaults.include?(p)) }
+      # @see CWM::Dialog#title
+      def title
+        # TODO: does it make sense also for the 3rd party addons?
+        _("Extension and Module Selection")
       end
 
-      # Dialog content
-      #
-      # @see ::UI::Dialog
-      def dialog_content
+      # @see CWM::Dialog#contents
+      def contents
         VBox(
           # TRANSLATORS: Product selection label (above a multi-selection box)
           Left(Heading(_("Available Extensions and Modules"))),
-          VWeight(60, MinHeight(8,
-            MultiSelectionBox(
-              Id(:addon_repos),
-              Opt(:notify, :immediate),
-              "",
-              selection_content
-            ))),
-          VSpacing(0.4),
-          details_widget
+          addons_selector_widget
         )
       end
 
-      # select the dependent products for the active selection
-      def select_dependent_products
-        # select the dependent products
-        new_selection = current_selection
-
-        # the selection has not changed, nothing to do
-        return if new_selection == selected_products
-
-        # add the dependent items to the selected list
-        selected_items = Yast::UI.QueryWidget(Id(:addon_repos), :SelectedItems)
-        new_items = new_selection - selected_products
-        new_items.each do |p|
-          # the dependencies contain also the transitive (indirect) 
dependencies,
-          # we do not need to recursively evaluate the list
-          dependencies = p&.details&.depends_on
-          selected_items.concat(dependencies) if dependencies
-        end
+      # Handler for the :next action
+      #
+      # Displays a confirmation popup if none product has been selected
+      #
+      # @return [Boolean] true when continuing; false if the action is canceled
+      def next_handler
+        read_user_selection
 
-        selected_items.uniq!
+        return true unless selected_products.empty?
 
-        Yast::UI.ChangeWidget(:addon_repos, :SelectedItems, selected_items)
+        Yast::Popup.ContinueCancel(continue_msg)
       end
 
-      # refresh the details of the currently selected add-on
-      def refresh_details(current_product)
-        details = product_description(current_product)
-        Yast::UI.ChangeWidget(Id(:details), :Value, details)
-        Yast::UI.ChangeWidget(Id(:details), :Enabled, true)
-      end
-
-      def read_user_selection
-        self.selected_products = current_selection
+      # Handler for the :abort action
+      #
+      # Displays a confirmation popup when running in the initial stage 
(inst-sys)
+      #
+      # @return [Boolean] true when aborting is confirmed; false otherwise
+      def abort_handler
+        return true unless Yast::Stage.initial
 
-        log.info("Selected products: #{selected_products.inspect}")
+        Yast::Popup.ConfirmAbort(:painless)
       end
 
+      # Text to display when the help button is pressed
       #
-      # The currently selected products
-      #
-      # @return [Array<Y2Packager::ProductLocation>] list of selected products
-      #
-      def current_selection
-        selected_items = Yast::UI.QueryWidget(Id(:addon_repos), :SelectedItems)
-        products.select { |p| selected_items.include?(p.dir) }
+      # @return [String] help
+      def help
+        [
+          # TRANSLATORS: Help text for the product selector dialog
+          _("<p>The selected repository contains several products in 
independent " \
+          "subdirectories. Select which products you want to install.</p>"),
+          # TRANSLATORS: Help text explaining different product selection 
statuses
+          _("<p>Bear in mind that products can have several states depending 
on " \
+            "how they were selected to be installed or not. Basically, it can 
be "\
+            "auto-selected by a pre-selection of recommended products or as a 
dependency "\
+            "of another product, manually selected by the user, or not 
selected "\
+            "(see the legend below).</p>")
+        ].join
       end
 
-      # Dialog title
+    private
+
+      # @return [Array<Y2Packager::ProductLocation>] collection of selected 
products
+      attr_writer :selected_products
+
+      # Addons selector widget
       #
-      # @see ::UI::Dialog
-      def dialog_title
-        # TODO: does it make sense also for the 3rd party addons?
-        _("Extension and Module Selection")
+      # @return [Y2Packager::Widgets::AddonsSelector]
+      def addons_selector_widget
+        @addons_selector_widget ||= Widgets::AddonsSelector.new(products, 
preselected_products)
       end
 
-      # A message for asking the user whether to continue without adding any 
addon.
+      # Reads the currently selected products
+      def read_user_selection
+        selected_items = addons_selector_widget.selected_items.map(&:id)
+
+        self.selected_products = products.select { |p| 
selected_items.include?(p.dir) }
+
+        log.info("Selected products: #{selected_products.inspect}")
+      end
+
+      # A message for asking the user whether to continue without adding any 
addon
       #
       # @return [String] translated message
       def continue_msg
@@ -201,55 +155,30 @@
           "Do you really want to continue without adding any product?")
       end
 
-      # description widget
-      # @return [Yast::Term] the addon details widget
-      def details_widget
-        VWeight(
-          40,
-          RichText(Id(:details), Opt(:disabled), initial_description)
-        )
-      end
-
-      # extra help text
-      # @return [String] first product description
-      def initial_description
-        return "" if products.empty?
-
-        product_description(products.first)
-      end
-
-      def product_description(product)
-        erb_file = File.join(__dir__, "product_summary.erb")
-        log.info "Loading ERB template #{erb_file}"
-        erb = ERB.new(File.read(erb_file))
-
-        # compute the dependent products
-        dependencies = []
-        product&.details&.depends_on&.each do |p|
-          # display the human readable product name instead of the product 
directory
-          prod = @products.find { |pr| pr.dir == p }
-          dependencies << (prod.summary || prod.name) if prod
-        end
-
-        # render the ERB template in the context of this object
-        erb.result(binding)
-      end
-
-      # return a list of the preselected products depending on the 
installation mode
-      # @return [Array<Y2Packager::ProductLocation>] the products
+      # Returns a list of the preselected products depending on the 
installation mode
+      #
+      # @see #preselected_installation_products
+      # @see #preselected_upgrade_products
+      #
+      # @return [Array<Y2Packager::ProductLocation>] preselected products
       def preselected_products
-        # at upgrade preselect the installed addons
-        return preselected_upgrade_products if Yast::Mode.update
-        # in installation preselect the defaults defined in the 
control.xml/installation.xml
-        return preselected_installation_products if Yast::Mode.installation
-
-        # in other modes (e.g. installed system) do not preselect anything
-        []
+        if Yast::Mode.installation
+          # in installation preselect the defaults defined in the 
control.xml/installation.xml
+          preselected_installation_products
+        elsif Yast::Mode.update
+          # at upgrade preselect the installed addons
+          preselected_upgrade_products
+        else
+          # in other modes (e.g. installed system) do not preselect anything
+          []
+        end
       end
 
-      # return a list of the preselected products at upgrade,
-      # preselect the installed products
-      # @return [Array<Y2Packager::ProductLocation>] the products
+      # Returns a list of the preselected products at upgrade
+      #
+      # Preselect the installed products
+      #
+      # @return [Array<Y2Packager::ProductLocation>] preselected products
       def preselected_upgrade_products
         missing_products = Yast::AddOnProduct.missing_upgrades
         # installed but not selected yet products (to avoid duplicates)
@@ -258,10 +187,12 @@
         end
       end
 
-      # return a list of the preselected products at installation,
-      # preselect the default products specified in the 
control.xml/installation.xml,
-      # the already selected products are ignored
-      # @return [Array<Y2Packager::ProductLocation>] the products
+      # Return a list of the preselected products at installation,
+      #
+      # Preselect the default products specified in the 
control.xml/installation.xml,
+      # the already selected products are ignored.
+      #
+      # @return [Array<Y2Packager::ProductLocation>] preselected products
       def preselected_installation_products
         default_modules = Yast::ProductFeatures.GetFeature("software", 
"default_modules")
         return [] unless default_modules.is_a?(Array)
@@ -278,14 +209,6 @@
           default_modules.include?(p.details&.product)
         end
       end
-
-      # Returns the current product (the one which has the focus in the addons 
list)
-      #
-      # @return [Y2Packager::Product,nil]
-      def find_current_product
-        current_item = Yast::UI.QueryWidget(Id(:addon_repos), :CurrentItem)
-        products.find { |p| p.dir == current_item }
-      end
     end
   end
 end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-packager-4.2.58/src/lib/y2packager/dialogs/product_summary.erb 
new/yast2-packager-4.2.59/src/lib/y2packager/dialogs/product_summary.erb
--- old/yast2-packager-4.2.58/src/lib/y2packager/dialogs/product_summary.erb    
2020-03-06 13:22:46.000000000 +0100
+++ new/yast2-packager-4.2.59/src/lib/y2packager/dialogs/product_summary.erb    
1970-01-01 01:00:00.000000000 +0100
@@ -1,36 +0,0 @@
-<%
-textdomain "packager"
-%>
-
-<%# TRANSLATORS: the RichText header, followed by the name of the directory %>
-<b><%= _("Directory on the Media:") %></b> <%= h(product.dir) %><br>
-<%# TRANSLATORS: the RichText header, followed by the name of the medium %>
-<b><%= _("Media Name:") %></b> <%= h(product.name) %><br>
-
-<% details = product.details %>
-<% if details %>
-  <% if details.product %>
-    <%# TRANSLATORS: the RichText header, followed by the product identifier, 
e.g. "SLES" %>
-    <b><%= _("Product ID:") %></b> <%= h(details.product) %>
-  <% end %>
-
-  <% if dependencies.nil? %>
-    <%# TRANSLATORS: error message in a RichText summary %>
-    <p><em><%= _("Cannot evaluate the product dependencies.") %></em></p>
-  <% elsif !dependencies.empty? %>
-    <%# TRANSLATORS: the RichText section header, followed by the names of the 
dependent products %>
-    <h3><%= _("Dependencies") %></h3>
-    <ul>
-      <% dependencies.each do |dep| %>
-        <li><%= h(dep) %></li>
-      <% end %>
-    </ul>
-  <% end %>
-
-  <% if !details.description.empty? %>
-    <%# TRANSLATORS: the RichText header, it is followed by an untranslated 
(English) product description %>
-    <h3><%= _("Product Description (English Only)") %></h3>
-    <%# no escaping, the description already *is* a rich text %>
-    <p><%= details.description %></p>
-  <% end %>
-<% end %>
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-packager-4.2.58/src/lib/y2packager/widgets/addons_selector.rb 
new/yast2-packager-4.2.59/src/lib/y2packager/widgets/addons_selector.rb
--- old/yast2-packager-4.2.58/src/lib/y2packager/widgets/addons_selector.rb     
1970-01-01 01:00:00.000000000 +0100
+++ new/yast2-packager-4.2.59/src/lib/y2packager/widgets/addons_selector.rb     
2020-03-18 15:04:45.000000000 +0100
@@ -0,0 +1,179 @@
+# Copyright (c) [2020] SUSE LLC
+#
+# All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of version 2 of the GNU General Public License as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, contact SUSE LLC.
+#
+# To contact SUSE LLC about this file by physical or electronic mail, you may
+# find current contact information at www.suse.com.
+
+require "yast"
+require "forwardable"
+require "cwm/multi_status_selector"
+
+module Y2Packager
+  module Widgets
+    # A custom widget to display a multi status selector list
+    class AddonsSelector < CWM::MultiStatusSelector
+      include Yast::Logger
+      include Yast::UIShortcuts
+
+      attr_reader :items
+
+      # Constructor
+      #
+      # @param products [Array<ProductLocation>] available product locations
+      # @param preselected_products [Array<ProductLocation>] product locations 
to be selected
+      def initialize(products, preselected_products)
+        @products = products
+        @items = products.map do |product|
+          dependencies = product&.details&.depends_on || []
+          selected = preselected_products.include?(product)
+
+          Item.new(product, dependencies, selected)
+        end
+      end
+
+      def init
+        super
+
+        # Respect the behavior introduced in version 4.2.55
+        details_widget.value = items.first.description
+      end
+
+      # (see CWM::AbstractWidget#contents)
+      def contents
+        VBox(
+          VWeight(60, super),
+          VWeight(40, details_widget)
+        )
+      end
+
+      # Toggles the item
+      #
+      # Also recalculates the dependencies to perform necessary auto selections
+      #
+      # @param item [Item] the item to toggle
+      def toggle(item)
+        item.toggle
+        refresh_details(item)
+        select_dependencies
+      end
+
+      # Returns selected and auto-selected items
+      #
+      # @return [Array<Item>] a collection of selected and auto-selected items
+      def selected_items
+        items.select { |i| i.selected? || i.auto_selected? }
+      end
+
+      # (see CWM::AbstractWidget#contents)
+      def help
+        Item.help
+      end
+
+    private
+
+      # (see CWM::MultiStatusSelector#label_event_handler)
+      def label_event_handler(item)
+        refresh_details(item)
+      end
+
+      # Updates the details area with the given item description
+      #
+      # @param item [Item] selected item
+      def refresh_details(item)
+        details_widget.value = item.description
+      end
+
+      # Auto-selects needed dependencies
+      #
+      # Based in the current selection, auto selects dependencies not manually
+      # selected yet.
+      def select_dependencies
+        # Resets previous auto selection
+        @items.select(&:auto_selected?).each(&:unselect!)
+
+        # Recalculates missed dependencies
+        selected_items = @items.select(&:selected?)
+        dependencies = selected_items.flat_map(&:dependencies).uniq
+        missed_dependencies = dependencies - selected_items.map(&:id)
+
+        # Auto-selects them
+        @items.select { |i| missed_dependencies.include?(i.id) 
}.each(&:auto_select!)
+      end
+
+      # Returns the widget to display the details
+      #
+      # @return [CWM::RichText] the widget to display the details
+      def details_widget
+        @details_widget ||=
+          begin
+            w = CWM::RichText.new
+            w.widget_id = "details_area"
+            w
+          end
+      end
+
+      # Internal class to represent a {Y2Packager::ProductLocation} as 
selectable item
+      class Item < Item
+        include Yast::Logger
+        include ERB::Util
+        include Yast::I18n
+
+        # Constructor
+        #
+        # @param product [Y2Packager::ProductLocation] the product to be 
represented
+        # @param dependencies [Array<String>] a collection with the 
dependencies ids
+        # @param selected [Boolean] a flag indicating the initial status for 
the item
+        def initialize(product, dependencies, selected)
+          @product = product
+          @dependencies = dependencies
+          @status = selected ? :selected : :unselected
+        end
+
+        attr_reader :dependencies, :status
+
+        # Returns the item id
+        #
+        # @return [String] the item id
+        def id
+          product.dir
+        end
+
+        # Returns the item label
+        #
+        # @return [String] the item label
+        def label
+          product.summary || product.name
+        end
+
+        # Builds the item description
+        def description
+          @description ||=
+            begin
+              erb_file = File.join(__dir__, "product_summary.erb")
+              log.info "Loading ERB template #{erb_file}"
+              erb = ERB.new(File.read(erb_file))
+
+              erb.result(binding)
+            end
+        end
+
+      private
+
+        attr_reader :product
+      end
+    end
+  end
+end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-packager-4.2.58/src/lib/y2packager/widgets/product_summary.erb 
new/yast2-packager-4.2.59/src/lib/y2packager/widgets/product_summary.erb
--- old/yast2-packager-4.2.58/src/lib/y2packager/widgets/product_summary.erb    
1970-01-01 01:00:00.000000000 +0100
+++ new/yast2-packager-4.2.59/src/lib/y2packager/widgets/product_summary.erb    
2020-03-18 15:04:45.000000000 +0100
@@ -0,0 +1,36 @@
+<%
+textdomain "packager"
+%>
+
+<%# TRANSLATORS: the RichText header, followed by the name of the directory %>
+<b><%= _("Directory on the Media:") %></b> <%= h(product.dir) %><br>
+<%# TRANSLATORS: the RichText header, followed by the name of the medium %>
+<b><%= _("Media Name:") %></b> <%= h(product.name) %><br>
+
+<% details = product.details %>
+<% if details %>
+  <% if details.product %>
+    <%# TRANSLATORS: the RichText header, followed by the product identifier, 
e.g. "SLES" %>
+    <b><%= _("Product ID:") %></b> <%= h(details.product) %>
+  <% end %>
+
+  <% if dependencies.nil? %>
+    <%# TRANSLATORS: error message in a RichText summary %>
+    <p><em><%= _("Cannot evaluate the product dependencies.") %></em></p>
+  <% elsif !dependencies.empty? %>
+    <%# TRANSLATORS: the RichText section header, followed by the names of the 
dependent products %>
+    <h3><%= _("Dependencies") %></h3>
+    <ul>
+      <% dependencies.each do |dep| %>
+        <li><%= h(dep) %></li>
+      <% end %>
+    </ul>
+  <% end %>
+
+  <% if !details.description.empty? %>
+    <%# TRANSLATORS: the RichText header, it is followed by an untranslated 
(English) product description %>
+    <h3><%= _("Product Description (English Only)") %></h3>
+    <%# no escaping, the description already *is* a rich text %>
+    <p><%= details.description %></p>
+  <% end %>
+<% end %>
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yast2-packager-4.2.58/test/addon_selector_test.rb 
new/yast2-packager-4.2.59/test/addon_selector_test.rb
--- old/yast2-packager-4.2.58/test/addon_selector_test.rb       2020-03-06 
13:22:46.000000000 +0100
+++ new/yast2-packager-4.2.59/test/addon_selector_test.rb       1970-01-01 
01:00:00.000000000 +0100
@@ -1,136 +0,0 @@
-#! /usr/bin/env rspec
-
-require_relative "./test_helper"
-
-require "y2packager/product_location"
-require "y2packager/product_location_details"
-require "y2packager/dialogs/addon_selector"
-
-describe Y2Packager::Dialogs::AddonSelector do
-  let(:media_products) do
-    prods = [
-      [
-        "SLE-15-Module-Basesystem 15.0-0",
-        "/Basesystem",
-        Y2Packager::ProductLocationDetails.new(product: 
"sle-module-basesystem")
-      ],
-      [
-        "SLE-15-Module-Legacy 15.0-0",
-        "/Legacy",
-        Y2Packager::ProductLocationDetails.new(product: "sle-module-legacy")
-      ]
-    ]
-    prods.map { |r| Y2Packager::ProductLocation.new(r[0], r[1], product: r[2]) 
}
-  end
-
-  subject { described_class.new(media_products) }
-
-  describe "#help_text" do
-    it "returns a String" do
-      expect(subject.help_text).to be_a(String)
-    end
-  end
-
-  describe "#abort_handler" do
-    it "returns :abort" do
-      allow(Yast::Stage).to receive(:initial).and_return(false)
-      expect(subject.abort_handler).to eq(:abort)
-    end
-
-    context "in installation" do
-      before do
-        expect(Yast::Stage).to receive(:initial).and_return(true)
-      end
-
-      it "asks for confirmation" do
-        expect(Yast::Popup).to receive(:ConfirmAbort).and_return(true)
-        subject.abort_handler
-      end
-
-      it "returns :abort when confirmed" do
-        expect(Yast::Popup).to receive(:ConfirmAbort).and_return(true)
-        expect(subject.abort_handler).to eq(:abort)
-      end
-
-      it "returns nil when not confirmed" do
-        expect(Yast::Popup).to receive(:ConfirmAbort).and_return(false)
-        expect(subject.abort_handler).to be_nil
-      end
-    end
-  end
-
-  describe "#next_handler" do
-    context "an addon is selected" do
-      before do
-        expect(Yast::UI).to receive(:QueryWidget).with(Id(:addon_repos), 
:SelectedItems)
-          .and_return(["/Basesystem"])
-      end
-
-      it "returns :next if an addon is selected" do
-        expect(subject.next_handler).to eq(:next)
-      end
-
-      it "does not display any popup" do
-        expect(Yast::Popup).to_not receive(:anything)
-        subject.next_handler
-      end
-    end
-
-    context "no addon is selected" do
-      before do
-        expect(Yast::UI).to receive(:QueryWidget).with(Id(:addon_repos), 
:SelectedItems)
-          .and_return([])
-      end
-
-      it "displays a popup asking for confirmation" do
-        expect(Yast::Popup).to receive(:ContinueCancel).with(/no product/i)
-        subject.next_handler
-      end
-
-      it "returns :next if the popup is confirmed" do
-        expect(Yast::Popup).to receive(:ContinueCancel).with(/no 
product/i).and_return(true)
-        expect(subject.next_handler).to eq(:next)
-      end
-
-      it "returns nil if the popup is not confirmed" do
-        expect(Yast::Popup).to receive(:ContinueCancel).with(/no 
product/i).and_return(false)
-        expect(subject.next_handler).to be_nil
-      end
-    end
-  end
-
-  describe "#create_dialog" do
-    context "in installation" do
-      before do
-        allow(Yast::Stage).to receive(:initial).and_return(true)
-        allow(Yast::Mode).to receive(:installation).and_return(true)
-      end
-
-      it "preselects the default products from control.xml" do
-        # mock the control.xml default
-        expect(Yast::ProductFeatures).to receive(:GetFeature)
-          .with("software", 
"default_modules").and_return(["sle-module-basesystem"])
-
-        allow(Y2Packager::Resolvable).to receive(:find)
-          .with(kind: :product, status: :selected).and_return([])
-
-        expect(Yast::Wizard).to receive(:SetContents) do |_title, content, 
_help, _back, _next|
-          # find the MultiSelectionBox term in the UI definition
-          term = content.nested_find do |t|
-            t.respond_to?(:value) && t.value == :MultiSelectionBox
-          end
-
-          # verify that the Basesystem module is preselected
-          expect(term.params[3][0].params[1]).to eq("SLE-15-Module-Basesystem 
15.0-0")
-          expect(term.params[3][0].params[2]).to eq(true)
-
-          # verify that the Legacy module is NOT preselected
-          expect(term.params[3][1].params[1]).to eq("SLE-15-Module-Legacy 
15.0-0")
-          expect(term.params[3][1].params[2]).to eq(false)
-        end
-
-        subject.create_dialog
-      end
-    end
-  end
-end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-packager-4.2.58/test/lib/dialogs/addon_selector_test.rb 
new/yast2-packager-4.2.59/test/lib/dialogs/addon_selector_test.rb
--- old/yast2-packager-4.2.58/test/lib/dialogs/addon_selector_test.rb   
1970-01-01 01:00:00.000000000 +0100
+++ new/yast2-packager-4.2.59/test/lib/dialogs/addon_selector_test.rb   
2020-03-18 15:04:45.000000000 +0100
@@ -0,0 +1,176 @@
+# Copyright (c) [2017-2020] SUSE LLC
+#
+# All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of version 2 of the GNU General Public License as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, contact SUSE LLC.
+#
+# To contact SUSE LLC about this file by physical or electronic mail, you may
+# find current contact information at www.suse.com.
+#
+require_relative "../../test_helper"
+
+require "cwm/rspec"
+require "y2packager/product_location"
+require "y2packager/product_location_details"
+require "y2packager/dialogs/addon_selector"
+
+describe Y2Packager::Dialogs::AddonSelector do
+  subject { described_class.new(products) }
+
+  include_examples "CWM::Dialog"
+
+  let(:basesystem) do
+    Y2Packager::ProductLocation.new(
+      "SLE-15-Module-Basesystem 15.0-0",
+      "/Basesystem",
+      product: Y2Packager::ProductLocationDetails.new(product: 
"sle-module-basesystem")
+    )
+  end
+
+  let(:desktop_applications) do
+    Y2Packager::ProductLocation.new(
+      "Desktop-Applications-Module 15-0",
+      "/Desktop-Applications",
+      product: Y2Packager::ProductLocationDetails.new(
+        depends_on: ["SLE-15-Module-Basesystem 15.0-0"]
+      )
+    )
+  end
+
+  let(:legacy_product) do
+    Y2Packager::ProductLocation.new(
+      "SLE-15-Module-Legacy 15.0-0",
+      "/Legacy",
+      product: Y2Packager::ProductLocationDetails.new(product: 
"sle-module-legacy")
+    )
+  end
+
+  let(:products) { [basesystem, desktop_applications, legacy_product] }
+
+  describe "#contents" do
+    context "during installation" do
+      before do
+        allow(Yast::Stage).to receive(:initial).and_return(true)
+        allow(Yast::Mode).to receive(:installation).and_return(true)
+        allow(Yast::UI).to receive(:UserInput).and_return(:next)
+
+        # mock the control.xml default
+        allow(Yast::ProductFeatures).to receive(:GetFeature)
+          .with("software", "default_modules")
+          .and_return(["sle-module-basesystem"])
+        allow(Y2Packager::Resolvable).to receive(:find)
+          .with(kind: :product, status: :selected)
+          .and_return([])
+      end
+
+      it "preselects the default products from control.xml" do
+        expect(Y2Packager::Widgets::AddonsSelector).to receive(:new)
+          .with(anything, [products.first])
+
+        subject.contents
+      end
+    end
+  end
+
+  describe "#abort_handler" do
+    it "returns :abort" do
+      allow(Yast::Stage).to receive(:initial).and_return(false)
+    end
+
+    context "during installation" do
+      let(:confirm_abort) { false }
+
+      before do
+        allow(Yast::Stage).to receive(:initial).and_return(true)
+        allow(Yast::Popup).to receive(:ConfirmAbort).and_return(confirm_abort)
+      end
+
+      it "asks for confirmation" do
+        expect(Yast::Popup).to receive(:ConfirmAbort).and_return(true)
+
+        subject.abort_handler
+      end
+
+      context "when confirmed" do
+        let(:confirm_abort) { true }
+
+        it "returns true" do
+          expect(subject.abort_handler).to eq(true)
+        end
+      end
+
+      context "when rejected" do
+        let(:confirm_abort) { false }
+
+        it "returns false" do
+          expect(subject.abort_handler).to eq(false)
+        end
+      end
+    end
+  end
+
+  describe "#next_handler" do
+    let(:addons_selector) { Y2Packager::Widgets::AddonsSelector.new(products, 
[]) }
+
+    before do
+      allow(Y2Packager::Widgets::AddonsSelector).to 
receive(:new).and_return(addons_selector)
+    end
+
+    context "when a product is selected" do
+      before do
+        addons_selector.items.each(&:select!)
+      end
+
+      it "does not display a popup" do
+        expect(Yast::Popup).to_not receive(:ContinueCancel)
+
+        subject.next_handler
+      end
+
+      it "returns true" do
+        expect(subject.next_handler).to eq(true)
+      end
+    end
+
+    context "when none product is selected" do
+      let(:confirm_continue) { false }
+
+      before do
+        addons_selector.items.each(&:unselect!)
+        allow(Yast::Popup).to 
receive(:ContinueCancel).and_return(confirm_continue)
+      end
+
+      it "displays a popup asking for confirmation" do
+        expect(Yast::Popup).to receive(:ContinueCancel).with(/no product/i)
+
+        subject.next_handler
+      end
+
+      context "and the user decides to continue" do
+        let(:confirm_continue) { true }
+
+        it "returns true" do
+          expect(subject.next_handler).to eq(true)
+        end
+      end
+
+      context "but the user decides to cancel" do
+        let(:confirm_continue) { false }
+
+        it "returns false" do
+          expect(subject.next_handler).to eq(false)
+        end
+      end
+    end
+  end
+end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-packager-4.2.58/test/lib/widgets/addons_selector_test.rb 
new/yast2-packager-4.2.59/test/lib/widgets/addons_selector_test.rb
--- old/yast2-packager-4.2.58/test/lib/widgets/addons_selector_test.rb  
1970-01-01 01:00:00.000000000 +0100
+++ new/yast2-packager-4.2.59/test/lib/widgets/addons_selector_test.rb  
2020-03-18 15:04:45.000000000 +0100
@@ -0,0 +1,183 @@
+# Copyright (c) [2020] SUSE LLC
+#
+# All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of version 2 of the GNU General Public License as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, contact SUSE LLC.
+#
+# To contact SUSE LLC about this file by physical or electronic mail, you may
+# find current contact information at www.suse.com.
+
+require_relative "../../test_helper"
+
+require "cwm/rspec"
+require "y2packager/product_location"
+require "y2packager/product_location_details"
+require "y2packager/widgets/addons_selector"
+
+describe Y2Packager::Widgets::AddonsSelector do
+  subject(:addons_selector) { described_class.new(products, 
preselected_products) }
+
+  include_examples "CWM::CustomWidget"
+
+  let(:details_area) do
+    subject.contents.nested_find { |i| i.is_a?(CWM::RichText) && i.widget_id 
== "details_area" }
+  end
+
+  let(:basesystem) do
+    Y2Packager::ProductLocation.new(
+      "SLE-15-Module-Basesystem 15.0-0",
+      "/Basesystem",
+      product: Y2Packager::ProductLocationDetails.new(product: 
"sle-module-basesystem")
+    )
+  end
+
+  let(:desktop_applications) do
+    Y2Packager::ProductLocation.new(
+      "Desktop-Applications-Module 15-0",
+      "/Desktop-Applications",
+      product: Y2Packager::ProductLocationDetails.new(
+        depends_on: ["/Basesystem"]
+      )
+    )
+  end
+
+  let(:legacy_product) do
+    Y2Packager::ProductLocation.new(
+      "SLE-15-Module-Legacy 15.0-0",
+      "/Legacy",
+      product: Y2Packager::ProductLocationDetails.new(product: 
"sle-module-legacy")
+    )
+  end
+
+  let(:products) { [basesystem, desktop_applications, legacy_product] }
+  let(:preselected_products) { [legacy_product] }
+
+  describe "#initialize" do
+    it "selects preselected products" do
+      expect(subject.selected_items.map(&:id)).to 
eq(preselected_products.map(&:dir))
+    end
+  end
+
+  describe "#init" do
+    let(:first_item) { subject.items.first }
+
+    # Behavior introduced in version 4.2.55 
(https://github.com/yast/yast-packager/pull/511)
+    it "displays the first item description" do
+      expect(details_area).to receive(:value=).with(first_item.description)
+
+      subject.init
+    end
+  end
+
+  describe "#items" do
+    it "returns a collection of items representing available products" do
+      expect(subject.items.map(&:id)).to eq(products.map(&:dir))
+    end
+  end
+
+  describe "#toggle" do
+    let(:item) { subject.items.first }
+
+    it "toggles given item" do
+      expect(item).to receive(:toggle)
+
+      subject.toggle(item)
+    end
+
+    it "displays the item details" do
+      expect(details_area).to receive(:value=).with(item.description)
+
+      subject.toggle(item)
+    end
+
+    context "when selected item has dependencies" do
+      let(:basesystem_item) { subject.items.find { |i| i.id == "/Basesystem" } 
}
+      let(:desktop_apps_item) { subject.items.find { |i| i.id == 
"/Desktop-Applications" } }
+
+      context "and they are not selected yet" do
+        it "auto-selects them" do
+          expect(basesystem_item).to receive(:auto_select!)
+
+          subject.toggle(desktop_apps_item)
+        end
+      end
+
+      context "but they are already selected" do
+        before do
+          basesystem_item.select!
+        end
+
+        it "does nothing" do
+          expect(basesystem_item).to_not receive(:auto_select!)
+          expect(basesystem_item).to_not receive(:unselect!)
+          expect(basesystem_item).to_not receive(:select!)
+
+          subject.toggle(desktop_apps_item)
+        end
+      end
+    end
+  end
+end
+
+describe Y2Packager::Widgets::AddonsSelector::Item do
+  subject(:item) { described_class.new(product, dependencies, selected) }
+
+  let(:product) do
+    Y2Packager::ProductLocation.new(
+      "SLE-15-Module-Basesystem 15.0-0",
+      "/Basesystem",
+      product: Y2Packager::ProductLocationDetails.new(product: 
"sle-module-basesystem")
+    )
+  end
+
+  let(:dependencies) { nil }
+  let(:selected) { false }
+
+  describe "#id" do
+    it "returns a String" do
+      expect(subject.id).to be_a(String)
+    end
+
+    it "returns the product dir" do
+      expect(subject.id).to eq(product.dir)
+    end
+  end
+
+  describe "#label" do
+    let(:product_summary) { "A product summary" }
+    let(:product_name) { "A product name" }
+
+    before do
+      allow(product).to receive(:summary).and_return(product_summary)
+      allow(product).to receive(:name).and_return(product_name)
+    end
+
+    it "returns a String" do
+      expect(subject.id).to be_a(String)
+    end
+
+    context "when product has a summary" do
+      it "returns the product summary" do
+        expect(subject.label).to eq(product_summary)
+      end
+    end
+
+    context "when product has not a summary" do
+      let(:product_summary) { nil }
+
+      it "returns the product name" do
+        expect(subject.label).to eq(product_name)
+      end
+    end
+  end
+end


Reply via email to