Skip to content

Commit

Permalink
Add a new widget to manage the addons selection
Browse files Browse the repository at this point in the history
  • Loading branch information
dgdavid committed Mar 12, 2020
1 parent e3d5922 commit ce545f3
Show file tree
Hide file tree
Showing 3 changed files with 350 additions and 0 deletions.
178 changes: 178 additions & 0 deletions src/lib/y2packager/widgets/addons_selector.rb
@@ -0,0 +1,178 @@
# 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

# (see CWM::AbstractWidget#contents)
def contents
VBox(
VWeight(60, super),
VWeight(
40,
VBox(
Left(Label("Details (English only)")),
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?).map(&:unselect!)

# Recalculates missed dependencies
selected_items = @items.select(&:selected?)
dependencies = selected_items.flat_map(&:dependencies).uniq
missed_dependencies = dependencies - selected_items.map(&:label)

# Auto-selects them
@items.select { |i| missed_dependencies.include?(i.label) }.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
172 changes: 172 additions & 0 deletions test/lib/widgets/addons_selector_test.rb
@@ -0,0 +1,172 @@
# 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: ["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] }
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 "#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

0 comments on commit ce545f3

Please sign in to comment.