twitter-bootstrap-rails 5.3.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +81 -0
- data/README.md +128 -628
- data/app/assets/javascripts/twitter/bootstrap/bootstrap.min.js +3 -3
- data/app/assets/stylesheets/twitter-bootstrap-static/bootstrap.css +3 -3
- data/lib/generators/bootstrap/badge/badge_generator.rb +13 -0
- data/lib/generators/bootstrap/breadcrumb/breadcrumb_generator.rb +13 -0
- data/lib/generators/bootstrap/button/button_generator.rb +13 -0
- data/lib/generators/bootstrap/cdn/cdn_generator.rb +39 -0
- data/lib/generators/bootstrap/cdn_tags.rb +43 -0
- data/lib/generators/bootstrap/dropdown/dropdown_generator.rb +13 -0
- data/lib/generators/bootstrap/features/features_generator.rb +13 -0
- data/lib/generators/bootstrap/footer/footer_generator.rb +13 -0
- data/lib/generators/bootstrap/header/header_generator.rb +13 -0
- data/lib/generators/bootstrap/hero/hero_generator.rb +13 -0
- data/lib/generators/bootstrap/install/install_generator.rb +120 -61
- data/lib/generators/bootstrap/install/templates/application.js +1 -1
- data/lib/generators/bootstrap/install/templates/bootstrap.js +10 -3
- data/lib/generators/bootstrap/install/templates/bootstrap_and_overrides.css +2 -2
- data/lib/generators/bootstrap/jumbotron/jumbotron_generator.rb +13 -0
- data/lib/generators/bootstrap/layout/layout_generator.rb +65 -1
- data/lib/generators/bootstrap/layout/templates/layout.html.erb +14 -9
- data/lib/generators/bootstrap/layout/templates/layout.html.haml +28 -43
- data/lib/generators/bootstrap/layout/templates/layout.html.slim +25 -40
- data/lib/generators/bootstrap/list_group/list_group_generator.rb +13 -0
- data/lib/generators/bootstrap/modal/modal_generator.rb +13 -0
- data/lib/generators/bootstrap/sidebar/sidebar_generator.rb +13 -0
- data/lib/generators/bootstrap/snippets/snippet_base.rb +126 -0
- data/lib/generators/bootstrap/snippets/snippet_catalog.rb +134 -0
- data/lib/generators/bootstrap/snippets/templates/badges/pills.html.erb +10 -0
- data/lib/generators/bootstrap/snippets/templates/badges/subtle.html.erb +10 -0
- data/lib/generators/bootstrap/snippets/templates/badges/subtle_bordered.html.erb +20 -0
- data/lib/generators/bootstrap/snippets/templates/badges/with_avatar.html.erb +34 -0
- data/lib/generators/bootstrap/snippets/templates/badges/with_avatar_divider.html.erb +66 -0
- data/lib/generators/bootstrap/snippets/templates/badges/with_icons.html.erb +26 -0
- data/lib/generators/bootstrap/snippets/templates/breadcrumbs/basic.html.erb +9 -0
- data/lib/generators/bootstrap/snippets/templates/breadcrumbs/chevron.html.erb +18 -0
- data/lib/generators/bootstrap/snippets/templates/breadcrumbs/custom.html.erb +18 -0
- data/lib/generators/bootstrap/snippets/templates/breadcrumbs/with_icons.html.erb +18 -0
- data/lib/generators/bootstrap/snippets/templates/buttons/circle.html.erb +20 -0
- data/lib/generators/bootstrap/snippets/templates/buttons/grid.html.erb +6 -0
- data/lib/generators/bootstrap/snippets/templates/buttons/loading.html.erb +10 -0
- data/lib/generators/bootstrap/snippets/templates/buttons/pills.html.erb +11 -0
- data/lib/generators/bootstrap/snippets/templates/buttons/with_icons.html.erb +14 -0
- data/lib/generators/bootstrap/snippets/templates/dropdowns/calendar.html.erb +156 -0
- data/lib/generators/bootstrap/snippets/templates/dropdowns/mega_menu.html.erb +140 -0
- data/lib/generators/bootstrap/snippets/templates/dropdowns/simple.html.erb +20 -0
- data/lib/generators/bootstrap/snippets/templates/dropdowns/with_icons.html.erb +88 -0
- data/lib/generators/bootstrap/snippets/templates/dropdowns/with_search.html.erb +64 -0
- data/lib/generators/bootstrap/snippets/templates/features/columns_with_icons.html.erb +46 -0
- data/lib/generators/bootstrap/snippets/templates/features/custom_cards.html.erb +79 -0
- data/lib/generators/bootstrap/snippets/templates/features/hanging_icons.html.erb +41 -0
- data/lib/generators/bootstrap/snippets/templates/features/icon_grid.html.erb +77 -0
- data/lib/generators/bootstrap/snippets/templates/features/with_title.html.erb +50 -0
- data/lib/generators/bootstrap/snippets/templates/footers/columns.html.erb +43 -0
- data/lib/generators/bootstrap/snippets/templates/footers/simple.html.erb +15 -0
- data/lib/generators/bootstrap/snippets/templates/footers/with_brand.html.erb +22 -0
- data/lib/generators/bootstrap/snippets/templates/footers/with_nav.html.erb +12 -0
- data/lib/generators/bootstrap/snippets/templates/footers/with_newsletter.html.erb +62 -0
- data/lib/generators/bootstrap/snippets/templates/headers/centered.html.erb +15 -0
- data/lib/generators/bootstrap/snippets/templates/headers/dark_double.html.erb +66 -0
- data/lib/generators/bootstrap/snippets/templates/headers/dark_search.html.erb +25 -0
- data/lib/generators/bootstrap/snippets/templates/headers/double.html.erb +28 -0
- data/lib/generators/bootstrap/snippets/templates/headers/grid_dropdown.html.erb +47 -0
- data/lib/generators/bootstrap/snippets/templates/headers/light_search.html.erb +34 -0
- data/lib/generators/bootstrap/snippets/templates/headers/nav_pills.html.erb +11 -0
- data/lib/generators/bootstrap/snippets/templates/headers/with_auth_buttons.html.erb +22 -0
- data/lib/generators/bootstrap/snippets/templates/heroes/centered.html.erb +13 -0
- data/lib/generators/bootstrap/snippets/templates/heroes/centered_screenshot.html.erb +17 -0
- data/lib/generators/bootstrap/snippets/templates/heroes/cropped_image.html.erb +17 -0
- data/lib/generators/bootstrap/snippets/templates/heroes/dark.html.erb +14 -0
- data/lib/generators/bootstrap/snippets/templates/heroes/signup_form.html.erb +26 -0
- data/lib/generators/bootstrap/snippets/templates/heroes/with_image.html.erb +17 -0
- data/lib/generators/bootstrap/snippets/templates/icons/_bootstrap_icons.html.erb +134 -0
- data/lib/generators/bootstrap/snippets/templates/jumbotrons/basic.html.erb +10 -0
- data/lib/generators/bootstrap/snippets/templates/jumbotrons/full_width.html.erb +12 -0
- data/lib/generators/bootstrap/snippets/templates/jumbotrons/placeholder.html.erb +13 -0
- data/lib/generators/bootstrap/snippets/templates/jumbotrons/with_icon.html.erb +22 -0
- data/lib/generators/bootstrap/snippets/templates/list-groups/checkable.html.erb +24 -0
- data/lib/generators/bootstrap/snippets/templates/list-groups/checkboxes.html.erb +45 -0
- data/lib/generators/bootstrap/snippets/templates/list-groups/checkboxes_expanded.html.erb +52 -0
- data/lib/generators/bootstrap/snippets/templates/list-groups/radios.html.erb +32 -0
- data/lib/generators/bootstrap/snippets/templates/list-groups/with_avatars.html.erb +36 -0
- data/lib/generators/bootstrap/snippets/templates/modals/confirm.html.erb +18 -0
- data/lib/generators/bootstrap/snippets/templates/modals/sheet.html.erb +19 -0
- data/lib/generators/bootstrap/snippets/templates/modals/signup.html.erb +44 -0
- data/lib/generators/bootstrap/snippets/templates/modals/whats_new.html.erb +41 -0
- data/lib/generators/bootstrap/snippets/templates/sidebars/collapsible.html.erb +90 -0
- data/lib/generators/bootstrap/snippets/templates/sidebars/dark.html.erb +67 -0
- data/lib/generators/bootstrap/snippets/templates/sidebars/icon_only.html.erb +59 -0
- data/lib/generators/bootstrap/snippets/templates/sidebars/light.html.erb +67 -0
- data/lib/generators/bootstrap/snippets/templates/sidebars/list_group.html.erb +94 -0
- data/lib/generators/bootstrap/starter/starter_generator.rb +37 -0
- data/lib/generators/bootstrap/starter/templates/starter.html.erb +13 -0
- data/lib/twitter/bootstrap/rails/bootstrap.rb +1 -0
- data/lib/twitter/bootstrap/rails/config.rb +54 -0
- data/lib/twitter/bootstrap/rails/engine.rb +0 -8
- data/lib/twitter/bootstrap/rails/version.rb +28 -1
- data/lib/twitter-bootstrap-rails.rb +0 -1
- data/spec/lib/generators/cdn_generator_spec.rb +42 -0
- data/spec/lib/generators/install_generator_spec.rb +148 -0
- data/spec/lib/generators/layout_generator_spec.rb +124 -0
- data/spec/lib/generators/no_hardcoded_versions_spec.rb +45 -0
- data/spec/lib/generators/snippet_generators_spec.rb +201 -0
- data/spec/lib/generators/starter_generator_spec.rb +54 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/support/generator_spec_helper.rb +87 -0
- data/vendor/assets/javascripts/twitter/bootstrap/bootstrap.bundle.min.js +3 -3
- data/vendor/assets/stylesheets/twitter/bootstrap/bootstrap.min.css +3 -3
- metadata +99 -123
- data/app/assets/fonts/glyphicons-halflings-regular.eot +0 -0
- data/app/assets/fonts/glyphicons-halflings-regular.svg +0 -288
- data/app/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/app/assets/fonts/glyphicons-halflings-regular.woff +0 -0
- data/app/assets/fonts/glyphicons-halflings-regular.woff2 +0 -0
- data/app/helpers/glyph_helper.rb +0 -23
- data/lib/generators/bootstrap/install/templates/bootstrap.coffee +0 -3
- data/lib/generators/bootstrap/install/templates/bootstrap_and_overrides.less +0 -21
- data/spec/lib/twitter_bootstrap_rails/glyph_helper_spec.rb +0 -24
- data/vendor/assets/stylesheets/twitter-bootstrap-static/bootstrap.css.erb +0 -6684
- data/vendor/static-source/bootstrap.less +0 -1
- data/vendor/static-source/sprites.less +0 -6
- data/vendor/toolkit/twitter/bootstrap/alerts.less +0 -73
- data/vendor/toolkit/twitter/bootstrap/badges.less +0 -66
- data/vendor/toolkit/twitter/bootstrap/bootstrap.less +0 -55
- data/vendor/toolkit/twitter/bootstrap/breadcrumbs.less +0 -26
- data/vendor/toolkit/twitter/bootstrap/button-groups.less +0 -244
- data/vendor/toolkit/twitter/bootstrap/buttons.less +0 -166
- data/vendor/toolkit/twitter/bootstrap/carousel.less +0 -270
- data/vendor/toolkit/twitter/bootstrap/close.less +0 -34
- data/vendor/toolkit/twitter/bootstrap/code.less +0 -69
- data/vendor/toolkit/twitter/bootstrap/component-animations.less +0 -33
- data/vendor/toolkit/twitter/bootstrap/dropdowns.less +0 -216
- data/vendor/toolkit/twitter/bootstrap/forms.less +0 -613
- data/vendor/toolkit/twitter/bootstrap/glyphicons.less +0 -305
- data/vendor/toolkit/twitter/bootstrap/grid.less +0 -84
- data/vendor/toolkit/twitter/bootstrap/input-groups.less +0 -171
- data/vendor/toolkit/twitter/bootstrap/jumbotron.less +0 -54
- data/vendor/toolkit/twitter/bootstrap/labels.less +0 -64
- data/vendor/toolkit/twitter/bootstrap/list-group.less +0 -130
- data/vendor/toolkit/twitter/bootstrap/media.less +0 -66
- data/vendor/toolkit/twitter/bootstrap/mixins/alerts.less +0 -14
- data/vendor/toolkit/twitter/bootstrap/mixins/background-variant.less +0 -9
- data/vendor/toolkit/twitter/bootstrap/mixins/border-radius.less +0 -18
- data/vendor/toolkit/twitter/bootstrap/mixins/buttons.less +0 -65
- data/vendor/toolkit/twitter/bootstrap/mixins/center-block.less +0 -7
- data/vendor/toolkit/twitter/bootstrap/mixins/clearfix.less +0 -22
- data/vendor/toolkit/twitter/bootstrap/mixins/forms.less +0 -85
- data/vendor/toolkit/twitter/bootstrap/mixins/gradients.less +0 -59
- data/vendor/toolkit/twitter/bootstrap/mixins/grid-framework.less +0 -91
- data/vendor/toolkit/twitter/bootstrap/mixins/grid.less +0 -122
- data/vendor/toolkit/twitter/bootstrap/mixins/hide-text.less +0 -21
- data/vendor/toolkit/twitter/bootstrap/mixins/image.less +0 -33
- data/vendor/toolkit/twitter/bootstrap/mixins/labels.less +0 -12
- data/vendor/toolkit/twitter/bootstrap/mixins/list-group.less +0 -30
- data/vendor/toolkit/twitter/bootstrap/mixins/nav-divider.less +0 -10
- data/vendor/toolkit/twitter/bootstrap/mixins/nav-vertical-align.less +0 -9
- data/vendor/toolkit/twitter/bootstrap/mixins/opacity.less +0 -8
- data/vendor/toolkit/twitter/bootstrap/mixins/pagination.less +0 -24
- data/vendor/toolkit/twitter/bootstrap/mixins/panels.less +0 -24
- data/vendor/toolkit/twitter/bootstrap/mixins/progress-bar.less +0 -10
- data/vendor/toolkit/twitter/bootstrap/mixins/reset-filter.less +0 -8
- data/vendor/toolkit/twitter/bootstrap/mixins/reset-text.less +0 -18
- data/vendor/toolkit/twitter/bootstrap/mixins/resize.less +0 -6
- data/vendor/toolkit/twitter/bootstrap/mixins/responsive-visibility.less +0 -15
- data/vendor/toolkit/twitter/bootstrap/mixins/size.less +0 -10
- data/vendor/toolkit/twitter/bootstrap/mixins/tab-focus.less +0 -9
- data/vendor/toolkit/twitter/bootstrap/mixins/table-row.less +0 -28
- data/vendor/toolkit/twitter/bootstrap/mixins/text-emphasis.less +0 -9
- data/vendor/toolkit/twitter/bootstrap/mixins/text-overflow.less +0 -8
- data/vendor/toolkit/twitter/bootstrap/mixins/vendor-prefixes.less +0 -227
- data/vendor/toolkit/twitter/bootstrap/mixins.less +0 -40
- data/vendor/toolkit/twitter/bootstrap/modals.less +0 -150
- data/vendor/toolkit/twitter/bootstrap/navbar.less +0 -660
- data/vendor/toolkit/twitter/bootstrap/navs.less +0 -242
- data/vendor/toolkit/twitter/bootstrap/normalize.less +0 -424
- data/vendor/toolkit/twitter/bootstrap/pager.less +0 -54
- data/vendor/toolkit/twitter/bootstrap/pagination.less +0 -89
- data/vendor/toolkit/twitter/bootstrap/panels.less +0 -271
- data/vendor/toolkit/twitter/bootstrap/popovers.less +0 -131
- data/vendor/toolkit/twitter/bootstrap/print.less +0 -101
- data/vendor/toolkit/twitter/bootstrap/progress-bars.less +0 -87
- data/vendor/toolkit/twitter/bootstrap/responsive-embed.less +0 -35
- data/vendor/toolkit/twitter/bootstrap/responsive-utilities.less +0 -194
- data/vendor/toolkit/twitter/bootstrap/scaffolding.less +0 -161
- data/vendor/toolkit/twitter/bootstrap/tables.less +0 -234
- data/vendor/toolkit/twitter/bootstrap/theme.less +0 -291
- data/vendor/toolkit/twitter/bootstrap/thumbnails.less +0 -36
- data/vendor/toolkit/twitter/bootstrap/tooltip.less +0 -101
- data/vendor/toolkit/twitter/bootstrap/type.less +0 -302
- data/vendor/toolkit/twitter/bootstrap/utilities.less +0 -55
- data/vendor/toolkit/twitter/bootstrap/variables.less +0 -1751
- data/vendor/toolkit/twitter/bootstrap/wells.less +0 -29
- data/vendor/toolkit/twitter/bootstrap_base.less +0 -2
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require "twitter/bootstrap/rails/version"
|
|
2
|
+
|
|
3
|
+
module Twitter
|
|
4
|
+
module Bootstrap
|
|
5
|
+
module Rails
|
|
6
|
+
# How the host app loads Bootstrap's assets. `rails g bootstrap:install cdn`
|
|
7
|
+
# writes an initializer setting this to :cdn; `bootstrap:layout` reads it to
|
|
8
|
+
# pick its default. Defaults to :static so the layout generator works whether
|
|
9
|
+
# or not that initializer exists.
|
|
10
|
+
VALID_ASSET_MODES = [:static, :cdn].freeze
|
|
11
|
+
|
|
12
|
+
@asset_mode = :static
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
attr_reader :asset_mode
|
|
16
|
+
|
|
17
|
+
def asset_mode=(mode)
|
|
18
|
+
mode = mode.to_sym
|
|
19
|
+
unless VALID_ASSET_MODES.include?(mode)
|
|
20
|
+
raise ArgumentError,
|
|
21
|
+
"asset_mode must be one of #{VALID_ASSET_MODES.inspect}, got #{mode.inspect}"
|
|
22
|
+
end
|
|
23
|
+
@asset_mode = mode
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def cdn?
|
|
27
|
+
asset_mode == :cdn
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Which asset pipeline the host app runs. Propshaft (the Rails 8 default)
|
|
31
|
+
# has no Sprockets directives: a `//= require` line in a manifest is an
|
|
32
|
+
# inert comment there, so assets have to be linked explicitly instead.
|
|
33
|
+
def asset_pipeline
|
|
34
|
+
return @asset_pipeline if defined?(@asset_pipeline) && @asset_pipeline
|
|
35
|
+
|
|
36
|
+
if defined?(::Propshaft)
|
|
37
|
+
:propshaft
|
|
38
|
+
elsif defined?(::Sprockets)
|
|
39
|
+
:sprockets
|
|
40
|
+
else
|
|
41
|
+
:unknown
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Set explicitly to override detection (used by the specs).
|
|
46
|
+
attr_writer :asset_pipeline
|
|
47
|
+
|
|
48
|
+
def propshaft?
|
|
49
|
+
asset_pipeline == :propshaft
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -12,14 +12,6 @@ module Twitter
|
|
|
12
12
|
module Bootstrap
|
|
13
13
|
module Rails
|
|
14
14
|
class Engine < ::Rails::Engine
|
|
15
|
-
initializer 'twitter-bootstrap-rails.setup',
|
|
16
|
-
:after => 'less-rails.after.load_config_initializers',
|
|
17
|
-
:group => :all do |app|
|
|
18
|
-
if defined?(Less)
|
|
19
|
-
app.config.less.paths << File.join(config.root, 'vendor', 'toolkit')
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
15
|
initializer 'twitter-bootstrap-rails.setup_helpers' do |app|
|
|
24
16
|
app.config.to_prepare do
|
|
25
17
|
ActionController::Base.send :include, Breadcrumbs
|
|
@@ -1,7 +1,34 @@
|
|
|
1
1
|
module Twitter
|
|
2
2
|
module Bootstrap
|
|
3
3
|
module Rails
|
|
4
|
-
VERSION = "5.
|
|
4
|
+
VERSION = "5.4.0"
|
|
5
|
+
|
|
6
|
+
# The Bootstrap release this gem vendors and links to. The gem version no
|
|
7
|
+
# longer tracks it; this constant is the authoritative statement.
|
|
8
|
+
BOOTSTRAP_VERSION = "5.3.8"
|
|
9
|
+
POPPER_VERSION = "2.11.8"
|
|
10
|
+
|
|
11
|
+
# URLs and Subresource Integrity hashes exactly as published on
|
|
12
|
+
# https://getbootstrap.com/docs/5.3/getting-started/introduction/
|
|
13
|
+
# Bumping Bootstrap means editing this hash and re-vendoring the dist files.
|
|
14
|
+
BOOTSTRAP_CDN = {
|
|
15
|
+
:css => {
|
|
16
|
+
:url => "https://cdn.jsdelivr.net/npm/bootstrap@#{BOOTSTRAP_VERSION}/dist/css/bootstrap.min.css",
|
|
17
|
+
:integrity => "sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB"
|
|
18
|
+
},
|
|
19
|
+
:bundle => {
|
|
20
|
+
:url => "https://cdn.jsdelivr.net/npm/bootstrap@#{BOOTSTRAP_VERSION}/dist/js/bootstrap.bundle.min.js",
|
|
21
|
+
:integrity => "sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
|
|
22
|
+
},
|
|
23
|
+
:js => {
|
|
24
|
+
:url => "https://cdn.jsdelivr.net/npm/bootstrap@#{BOOTSTRAP_VERSION}/dist/js/bootstrap.min.js",
|
|
25
|
+
:integrity => "sha384-G/EV+4j2dNv+tEPo3++6LCgdCROaejBqfUeNjuKAiuXbjrxilcCdDz6ZAVfHWe1Y"
|
|
26
|
+
},
|
|
27
|
+
:popper => {
|
|
28
|
+
:url => "https://cdn.jsdelivr.net/npm/@popperjs/core@#{POPPER_VERSION}/dist/umd/popper.min.js",
|
|
29
|
+
:integrity => "sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r"
|
|
30
|
+
}
|
|
31
|
+
}.freeze
|
|
5
32
|
end
|
|
6
33
|
end
|
|
7
34
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Bootstrap::Generators::CdnGenerator do
|
|
4
|
+
let(:cdn) { Twitter::Bootstrap::Rails::BOOTSTRAP_CDN }
|
|
5
|
+
|
|
6
|
+
it "prints the stylesheet link and the JS bundle by default" do
|
|
7
|
+
output = run_generator(described_class)
|
|
8
|
+
|
|
9
|
+
expect(output).to include(cdn[:css][:url])
|
|
10
|
+
expect(output).to include(cdn[:css][:integrity])
|
|
11
|
+
expect(output).to include(cdn[:bundle][:url])
|
|
12
|
+
expect(output).to include(cdn[:bundle][:integrity])
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "prints only the stylesheet link with --css" do
|
|
16
|
+
output = run_generator(described_class, %w[--css])
|
|
17
|
+
|
|
18
|
+
expect(output).to include(cdn[:css][:url])
|
|
19
|
+
expect(output).not_to include(cdn[:bundle][:url])
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "prints only the bundle script with --js" do
|
|
23
|
+
output = run_generator(described_class, %w[--js])
|
|
24
|
+
|
|
25
|
+
expect(output).to include(cdn[:bundle][:url])
|
|
26
|
+
expect(output).not_to include(cdn[:css][:url])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "prints the Popper pair with --separate-popper" do
|
|
30
|
+
output = run_generator(described_class, %w[--separate-popper])
|
|
31
|
+
|
|
32
|
+
expect(output).to include(cdn[:popper][:url])
|
|
33
|
+
expect(output).to include(cdn[:js][:url])
|
|
34
|
+
expect(output).not_to include(cdn[:bundle][:url])
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "writes nothing to disk" do
|
|
38
|
+
run_generator(described_class)
|
|
39
|
+
|
|
40
|
+
expect(Dir.children(destination_root)).to be_empty
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Bootstrap::Generators::InstallGenerator do
|
|
4
|
+
let(:vendored_css) { 'app/assets/stylesheets/twitter/bootstrap/bootstrap.min.css' }
|
|
5
|
+
let(:vendored_js) { 'app/assets/javascripts/twitter/bootstrap/bootstrap.bundle.min.js' }
|
|
6
|
+
|
|
7
|
+
describe "static (the default)" do
|
|
8
|
+
it "vendors both Bootstrap dist files into the app" do
|
|
9
|
+
run_generator(described_class, %w[static])
|
|
10
|
+
|
|
11
|
+
expect(destination_exist?(vendored_css)).to be true
|
|
12
|
+
expect(destination_exist?(vendored_js)).to be true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "vendors the Bootstrap version this gem declares" do
|
|
16
|
+
run_generator(described_class, %w[static])
|
|
17
|
+
|
|
18
|
+
expect(read_destination(vendored_css)).to include("Bootstrap v#{Twitter::Bootstrap::Rails::BOOTSTRAP_VERSION}")
|
|
19
|
+
expect(read_destination(vendored_js)).to include("Bootstrap v#{Twitter::Bootstrap::Rails::BOOTSTRAP_VERSION}")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "defaults to static when no mode is given" do
|
|
23
|
+
run_generator(described_class)
|
|
24
|
+
|
|
25
|
+
expect(destination_exist?(vendored_css)).to be true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "writes manifests that require the vendored files" do
|
|
29
|
+
run_generator(described_class, %w[static])
|
|
30
|
+
|
|
31
|
+
expect(read_destination('app/assets/javascripts/application.js'))
|
|
32
|
+
.to include("//= require twitter/bootstrap/bootstrap.bundle.min")
|
|
33
|
+
expect(read_destination('app/assets/stylesheets/bootstrap_and_overrides.css'))
|
|
34
|
+
.to include("*= require twitter/bootstrap/bootstrap.min")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "adds the require lines to manifests the app already has" do
|
|
38
|
+
write_destination('app/assets/javascripts/application.js', "// application\n//= require_tree .\n")
|
|
39
|
+
write_destination('app/assets/stylesheets/application.css', "/*\n *= require_self\n *= require_tree .\n*/\n")
|
|
40
|
+
|
|
41
|
+
run_generator(described_class, %w[static])
|
|
42
|
+
|
|
43
|
+
expect(read_destination('app/assets/javascripts/application.js'))
|
|
44
|
+
.to include("//= require twitter/bootstrap/bootstrap.bundle.min")
|
|
45
|
+
expect(read_destination('app/assets/stylesheets/application.css'))
|
|
46
|
+
.to include("*= require bootstrap_and_overrides")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "says what to add by hand when a manifest has no Sprockets anchor" do
|
|
50
|
+
# A Propshaft app's application.css is plain CSS with nothing to anchor to.
|
|
51
|
+
write_destination('app/assets/stylesheets/application.css', "/*\n * Propshaft manifest.\n */\n")
|
|
52
|
+
|
|
53
|
+
output = run_generator(described_class, %w[static])
|
|
54
|
+
|
|
55
|
+
expect(output).to match(/has no `require_self` directive to anchor to/)
|
|
56
|
+
expect(output).to include("*= require bootstrap_and_overrides")
|
|
57
|
+
expect(read_destination('app/assets/stylesheets/application.css')).not_to include("require bootstrap_and_overrides")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "does not duplicate require lines when run twice" do
|
|
61
|
+
run_generator(described_class, %w[static])
|
|
62
|
+
run_generator(described_class, %w[static])
|
|
63
|
+
|
|
64
|
+
js = read_destination('app/assets/javascripts/application.js')
|
|
65
|
+
expect(js.scan("require twitter/bootstrap/bootstrap.bundle.min").length).to eq(1)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "writes no CDN initializer" do
|
|
69
|
+
run_generator(described_class, %w[static])
|
|
70
|
+
|
|
71
|
+
expect(destination_exist?('config/initializers/bootstrap.rb')).to be false
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "raises rather than silently installing nothing when a dist file is missing" do
|
|
75
|
+
allow(File).to receive(:file?).and_call_original
|
|
76
|
+
allow(File).to receive(:file?).with(/bootstrap\.min\.css\z/).and_return(false)
|
|
77
|
+
|
|
78
|
+
expect {
|
|
79
|
+
run_generator(described_class, %w[static])
|
|
80
|
+
}.to raise_error(described_class::MissingAssetError, /bootstrap\.min\.css/)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Propshaft is the Rails 8 default and has no Sprockets directives: a
|
|
85
|
+
# `//= require` line there is an inert comment, so writing manifests that rely
|
|
86
|
+
# on one would ship an app with no Bootstrap at all.
|
|
87
|
+
describe "static on a Propshaft app" do
|
|
88
|
+
before { Twitter::Bootstrap::Rails.asset_pipeline = :propshaft }
|
|
89
|
+
|
|
90
|
+
it "still vendors both dist files" do
|
|
91
|
+
run_generator(described_class, %w[static])
|
|
92
|
+
|
|
93
|
+
expect(destination_exist?(vendored_css)).to be true
|
|
94
|
+
expect(destination_exist?(vendored_js)).to be true
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "writes no Sprockets manifests" do
|
|
98
|
+
run_generator(described_class, %w[static])
|
|
99
|
+
|
|
100
|
+
expect(destination_exist?('app/assets/javascripts/application.js')).to be false
|
|
101
|
+
expect(destination_exist?('app/assets/stylesheets/bootstrap_and_overrides.css')).to be false
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "does not touch an existing application.css" do
|
|
105
|
+
original = "/*\n * Propshaft manifest.\n */\n"
|
|
106
|
+
write_destination('app/assets/stylesheets/application.css', original)
|
|
107
|
+
|
|
108
|
+
run_generator(described_class, %w[static])
|
|
109
|
+
|
|
110
|
+
expect(read_destination('app/assets/stylesheets/application.css')).to eq(original)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "points the user at bootstrap:layout" do
|
|
114
|
+
output = run_generator(described_class, %w[static])
|
|
115
|
+
|
|
116
|
+
expect(output).to match(/vendored for Propshaft/)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
describe "cdn" do
|
|
121
|
+
it "vendors nothing" do
|
|
122
|
+
run_generator(described_class, %w[cdn])
|
|
123
|
+
|
|
124
|
+
expect(destination_exist?(vendored_css)).to be false
|
|
125
|
+
expect(destination_exist?(vendored_js)).to be false
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "writes an initializer selecting cdn mode" do
|
|
129
|
+
run_generator(described_class, %w[cdn])
|
|
130
|
+
|
|
131
|
+
expect(read_destination('config/initializers/bootstrap.rb'))
|
|
132
|
+
.to include("Twitter::Bootstrap::Rails.asset_mode = :cdn")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "creates no asset manifests" do
|
|
136
|
+
run_generator(described_class, %w[cdn])
|
|
137
|
+
|
|
138
|
+
expect(destination_exist?('app/assets/javascripts/application.js')).to be false
|
|
139
|
+
expect(destination_exist?('app/assets/stylesheets/bootstrap_and_overrides.css')).to be false
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it "rejects an unknown mode" do
|
|
144
|
+
expect {
|
|
145
|
+
run_generator(described_class, %w[less])
|
|
146
|
+
}.to raise_error(Rails::Generators::Error, /Expected 'static' or 'cdn'/)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Bootstrap::Generators::LayoutGenerator do
|
|
4
|
+
let(:cdn) { Twitter::Bootstrap::Rails::BOOTSTRAP_CDN }
|
|
5
|
+
|
|
6
|
+
# The generated layout for the given engine.
|
|
7
|
+
def generate_layout(args = [], engine: :erb)
|
|
8
|
+
stub_rails_application(engine)
|
|
9
|
+
run_generator(described_class, args)
|
|
10
|
+
read_destination("app/views/layouts/application.html.#{engine}")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
[:erb, :haml, :slim].each do |engine|
|
|
14
|
+
context "with the #{engine} template engine" do
|
|
15
|
+
it "uses the asset pipeline by default" do
|
|
16
|
+
layout = generate_layout([], :engine => engine)
|
|
17
|
+
|
|
18
|
+
expect(layout).to include('stylesheet_link_tag "application"')
|
|
19
|
+
expect(layout).to include('javascript_include_tag "application"')
|
|
20
|
+
expect(layout).not_to include("cdn.jsdelivr.net")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "emits the CDN link and bundle with --cdn" do
|
|
24
|
+
layout = generate_layout(%w[--cdn], :engine => engine)
|
|
25
|
+
|
|
26
|
+
expect(layout).to include(cdn[:css][:url])
|
|
27
|
+
expect(layout).to include(cdn[:css][:integrity])
|
|
28
|
+
expect(layout).to include(cdn[:bundle][:url])
|
|
29
|
+
expect(layout).to include(cdn[:bundle][:integrity])
|
|
30
|
+
expect(layout).to include('crossorigin="anonymous"')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "does not also link the pipeline when using --cdn" do
|
|
34
|
+
layout = generate_layout(%w[--cdn], :engine => engine)
|
|
35
|
+
|
|
36
|
+
expect(layout).not_to include('stylesheet_link_tag')
|
|
37
|
+
expect(layout).not_to include('javascript_include_tag')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "emits Popper and bootstrap.js instead of the bundle with --cdn --separate-popper" do
|
|
41
|
+
layout = generate_layout(%w[--cdn --separate-popper], :engine => engine)
|
|
42
|
+
|
|
43
|
+
expect(layout).to include(cdn[:popper][:url])
|
|
44
|
+
expect(layout).to include(cdn[:popper][:integrity])
|
|
45
|
+
expect(layout).to include(cdn[:js][:url])
|
|
46
|
+
expect(layout).to include(cdn[:js][:integrity])
|
|
47
|
+
expect(layout).not_to include(cdn[:bundle][:url])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "includes the important globals: doctype, html lang, and viewport" do
|
|
51
|
+
layout = generate_layout([], :engine => engine)
|
|
52
|
+
|
|
53
|
+
expect(layout).to match(/\A(<!doctype html>|!!!|doctype html)/)
|
|
54
|
+
expect(layout).to match(/lang[^\n]*en/)
|
|
55
|
+
expect(layout).to include("width=device-width, initial-scale=1")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "carries no Bootstrap 3 markup" do
|
|
59
|
+
layout = generate_layout([], :engine => engine)
|
|
60
|
+
|
|
61
|
+
expect(layout).not_to include("navbar-default")
|
|
62
|
+
expect(layout).not_to include("icon-bar")
|
|
63
|
+
expect(layout).not_to include("html5shiv")
|
|
64
|
+
expect(layout).not_to match(/\bwell\b/)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe "on a Propshaft app" do
|
|
70
|
+
before { Twitter::Bootstrap::Rails.asset_pipeline = :propshaft }
|
|
71
|
+
|
|
72
|
+
[:erb, :haml, :slim].each do |engine|
|
|
73
|
+
it "links the vendored Bootstrap explicitly in #{engine}, since require directives are inert" do
|
|
74
|
+
layout = generate_layout([], :engine => engine)
|
|
75
|
+
|
|
76
|
+
expect(layout).to include('stylesheet_link_tag "twitter/bootstrap/bootstrap.min", "application"')
|
|
77
|
+
expect(layout).to include('javascript_include_tag "twitter/bootstrap/bootstrap.bundle.min"')
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "does not link an application.js that Propshaft apps do not have" do
|
|
82
|
+
layout = generate_layout
|
|
83
|
+
|
|
84
|
+
expect(layout).not_to include('javascript_include_tag "twitter/bootstrap/bootstrap.bundle.min", "application"')
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "still uses the CDN when asked" do
|
|
88
|
+
layout = generate_layout(%w[--cdn])
|
|
89
|
+
|
|
90
|
+
expect(layout).to include(cdn[:css][:url])
|
|
91
|
+
expect(layout).not_to include('stylesheet_link_tag')
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "names the layout after the given argument" do
|
|
96
|
+
stub_rails_application(:erb)
|
|
97
|
+
run_generator(described_class, %w[admin])
|
|
98
|
+
|
|
99
|
+
expect(destination_exist?("app/views/layouts/admin.html.erb")).to be true
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "warns and falls back to the bundle when --separate-popper is used without --cdn" do
|
|
103
|
+
stub_rails_application(:erb)
|
|
104
|
+
output = run_generator(described_class, %w[--separate-popper])
|
|
105
|
+
|
|
106
|
+
expect(output).to match(/separate-popper only applies with --cdn/)
|
|
107
|
+
expect(read_destination("app/views/layouts/application.html.erb")).not_to include(cdn[:popper][:url])
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "defaults to CDN tags when bootstrap:install recorded cdn mode" do
|
|
111
|
+
Twitter::Bootstrap::Rails.asset_mode = :cdn
|
|
112
|
+
layout = generate_layout
|
|
113
|
+
|
|
114
|
+
expect(layout).to include(cdn[:css][:url])
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "lets --no-cdn override a recorded cdn mode" do
|
|
118
|
+
Twitter::Bootstrap::Rails.asset_mode = :cdn
|
|
119
|
+
layout = generate_layout(%w[--no-cdn])
|
|
120
|
+
|
|
121
|
+
expect(layout).to include('stylesheet_link_tag "application"')
|
|
122
|
+
expect(layout).not_to include("cdn.jsdelivr.net")
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
# Bumping Bootstrap should be a one-file change. If a template hardcodes a CDN
|
|
4
|
+
# URL or an integrity hash, that promise quietly breaks.
|
|
5
|
+
describe "generator templates" do
|
|
6
|
+
gem_root = File.expand_path("../../../..", __FILE__)
|
|
7
|
+
|
|
8
|
+
let(:template_files) do
|
|
9
|
+
Dir[File.join(gem_root, "lib/generators/**/templates/**/*")].select { |f| File.file?(f) }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "has templates to check" do
|
|
13
|
+
expect(template_files).not_to be_empty
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "hardcodes no Bootstrap CDN version" do
|
|
17
|
+
offenders = template_files.select { |f| File.read(f).include?("bootstrap@") }
|
|
18
|
+
|
|
19
|
+
expect(offenders).to be_empty,
|
|
20
|
+
"these templates hardcode a CDN version instead of using the constants: #{offenders.inspect}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "hardcodes no Subresource Integrity hash" do
|
|
24
|
+
offenders = template_files.select { |f| File.read(f).match?(/sha384-/) }
|
|
25
|
+
|
|
26
|
+
expect(offenders).to be_empty,
|
|
27
|
+
"these templates hardcode an integrity hash instead of using the constants: #{offenders.inspect}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "keeps the vendored assets on the version the gem declares" do
|
|
31
|
+
version = Twitter::Bootstrap::Rails::BOOTSTRAP_VERSION
|
|
32
|
+
|
|
33
|
+
{
|
|
34
|
+
"vendor/assets/stylesheets/twitter/bootstrap/bootstrap.min.css" => "Bootstrap v#{version}",
|
|
35
|
+
"vendor/assets/javascripts/twitter/bootstrap/bootstrap.bundle.min.js" => "Bootstrap v#{version}",
|
|
36
|
+
"app/assets/stylesheets/twitter-bootstrap-static/bootstrap.css" => "Bootstrap v#{version}",
|
|
37
|
+
"app/assets/javascripts/twitter/bootstrap/bootstrap.min.js" => "Bootstrap v#{version}"
|
|
38
|
+
}.each do |path, banner|
|
|
39
|
+
full = File.join(gem_root, path)
|
|
40
|
+
expect(File.exist?(full)).to be(true), "missing vendored asset #{path}"
|
|
41
|
+
expect(File.read(full, 400)).to include(banner),
|
|
42
|
+
"#{path} is not Bootstrap #{version}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe "Bootstrap snippet generators" do
|
|
4
|
+
Catalog = Bootstrap::Generators::SnippetCatalog
|
|
5
|
+
TEMPLATE_ROOT = File.expand_path("../../../../lib/generators/bootstrap/snippets/templates", __FILE__)
|
|
6
|
+
|
|
7
|
+
def generator_for(name)
|
|
8
|
+
Bootstrap::Generators.const_get("#{name.split('_').map(&:capitalize).join}Generator")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe "the catalog" do
|
|
12
|
+
it "covers all twelve snippet categories from the docs" do
|
|
13
|
+
expect(Catalog::GENERATORS.values.sort).to eq(%w[
|
|
14
|
+
badges breadcrumbs buttons dropdowns features footers headers heroes
|
|
15
|
+
jumbotrons list-groups modals sidebars
|
|
16
|
+
].sort)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "has a template file for every catalogued variant" do
|
|
20
|
+
missing = Catalog::VARIANTS.flat_map do |category, variants|
|
|
21
|
+
variants.map { |v| "#{category}/#{v[:name]}" }
|
|
22
|
+
.reject { |rel| File.exist?(File.join(TEMPLATE_ROOT, "#{rel}.html.erb")) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
expect(missing).to be_empty, "catalogued variants with no template: #{missing.inspect}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "has a catalog entry for every template file" do
|
|
29
|
+
on_disk = Dir[File.join(TEMPLATE_ROOT, "*", "*.html.erb")]
|
|
30
|
+
.reject { |f| File.basename(File.dirname(f)) == "icons" }
|
|
31
|
+
.map { |f| "#{File.basename(File.dirname(f))}/#{File.basename(f, '.html.erb')}" }
|
|
32
|
+
catalogued = Catalog::VARIANTS.flat_map { |c, vs| vs.map { |v| "#{c}/#{v[:name]}" } }
|
|
33
|
+
|
|
34
|
+
expect((on_disk - catalogued)).to be_empty,
|
|
35
|
+
"templates with no catalog entry: #{(on_disk - catalogued).inspect}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "flags exactly the variants whose markup references the icon sprite" do
|
|
39
|
+
Catalog::VARIANTS.each do |category, variants|
|
|
40
|
+
variants.each do |v|
|
|
41
|
+
markup = File.read(File.join(TEMPLATE_ROOT, category, "#{v[:name]}.html.erb"))
|
|
42
|
+
expect(markup.include?('xlink:href="#')).to eq(v[:icons]),
|
|
43
|
+
"#{category}/#{v[:name]} :icons flag is #{v[:icons]} but markup says otherwise"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "flags exactly the variants that reference docs-site placeholder images" do
|
|
49
|
+
placeholders = ["bootstrap-docs.png", "bootstrap-themes.png",
|
|
50
|
+
"/docs/5.3/assets/", "unsplash-photo-"]
|
|
51
|
+
|
|
52
|
+
Catalog::VARIANTS.each do |category, variants|
|
|
53
|
+
variants.each do |v|
|
|
54
|
+
markup = File.read(File.join(TEMPLATE_ROOT, category, "#{v[:name]}.html.erb"))
|
|
55
|
+
expect(placeholders.any? { |p| markup.include?(p) }).to eq(!!v[:images]),
|
|
56
|
+
"#{category}/#{v[:name]} :images flag is #{v[:images]} but markup says otherwise"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "gives every variant a unique name within its category" do
|
|
62
|
+
Catalog::VARIANTS.each do |category, variants|
|
|
63
|
+
names = variants.map { |v| v[:name] }
|
|
64
|
+
expect(names.uniq).to eq(names), "duplicate variant names in #{category}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe "the shipped snippet markup" do
|
|
70
|
+
let(:templates) { Dir[File.join(TEMPLATE_ROOT, "*", "*.html.erb")] }
|
|
71
|
+
|
|
72
|
+
it "carries no leftover docs page chrome" do
|
|
73
|
+
offenders = templates.select do |f|
|
|
74
|
+
body = File.read(f)
|
|
75
|
+
body.include?("b-example-divider") || body.include?("bd-mode-toggle") ||
|
|
76
|
+
body.match?(/<h1 class="visually-hidden">[^<]*examples/)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
expect(offenders).to be_empty, "page chrome leaked into: #{offenders.inspect}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "keeps viewBox correctly cased so icons render" do
|
|
83
|
+
offenders = templates.select { |f| File.read(f).include?("viewbox=") }
|
|
84
|
+
|
|
85
|
+
expect(offenders).to be_empty, "lowercased viewBox (icons will not render) in: #{offenders.inspect}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "references only icons that the sprite defines" do
|
|
89
|
+
sprite = File.read(File.join(TEMPLATE_ROOT, "icons", "_bootstrap_icons.html.erb"))
|
|
90
|
+
defined_ids = sprite.scan(/<symbol id="([^"]+)"/).flatten
|
|
91
|
+
|
|
92
|
+
referenced = templates.flat_map { |f| File.read(f).scan(/<use xlink:href="#([^"]+)"/).flatten }.uniq
|
|
93
|
+
|
|
94
|
+
expect(referenced - defined_ids).to be_empty,
|
|
95
|
+
"snippets reference icons missing from the sprite: #{(referenced - defined_ids).inspect}"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
describe "generating a snippet" do
|
|
100
|
+
it "writes the default variant as a partial" do
|
|
101
|
+
run_generator(generator_for("header"))
|
|
102
|
+
|
|
103
|
+
expect(destination_exist?('app/views/shared/_header.html.erb')).to be true
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "writes the requested variant" do
|
|
107
|
+
run_generator(generator_for("hero"), %w[dark])
|
|
108
|
+
|
|
109
|
+
partial = read_destination('app/views/shared/_hero.html.erb')
|
|
110
|
+
expected = File.read(File.join(TEMPLATE_ROOT, "heroes", "dark.html.erb"))
|
|
111
|
+
expect(partial).to eq(expected)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "accepts dashes in place of underscores" do
|
|
115
|
+
run_generator(generator_for("footer"), %w[with-newsletter])
|
|
116
|
+
|
|
117
|
+
expect(read_destination('app/views/shared/_footer.html.erb'))
|
|
118
|
+
.to eq(File.read(File.join(TEMPLATE_ROOT, "footers", "with_newsletter.html.erb")))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "honours --as" do
|
|
122
|
+
run_generator(generator_for("footer"), %w[columns --as=site_footer])
|
|
123
|
+
|
|
124
|
+
expect(destination_exist?('app/views/shared/_site_footer.html.erb')).to be true
|
|
125
|
+
expect(destination_exist?('app/views/shared/_footer.html.erb')).to be false
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "honours --path" do
|
|
129
|
+
run_generator(generator_for("badge"), %w[pills --path=app/views/components])
|
|
130
|
+
|
|
131
|
+
expect(destination_exist?('app/views/components/_badge.html.erb')).to be true
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it "tells the user how to render it" do
|
|
135
|
+
output = run_generator(generator_for("modal"), %w[confirm])
|
|
136
|
+
|
|
137
|
+
expect(output).to include('render "shared/modal"')
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "raises on an unknown variant, listing the valid ones" do
|
|
141
|
+
expect {
|
|
142
|
+
run_generator(generator_for("badge"), %w[neon])
|
|
143
|
+
}.to raise_error(Rails::Generators::Error, /Unknown badges variant "neon".*pills, subtle/m)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
describe "the icons sprite" do
|
|
148
|
+
it "is installed alongside a snippet that needs it" do
|
|
149
|
+
run_generator(generator_for("header"), %w[dark_search])
|
|
150
|
+
|
|
151
|
+
expect(destination_exist?('app/views/shared/_bootstrap_icons.html.erb')).to be true
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it "is not installed for a snippet that does not use icons" do
|
|
155
|
+
run_generator(generator_for("header"), %w[nav_pills])
|
|
156
|
+
|
|
157
|
+
expect(destination_exist?('app/views/shared/_bootstrap_icons.html.erb')).to be false
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "can be skipped with --no-icons" do
|
|
161
|
+
run_generator(generator_for("header"), %w[dark_search --no-icons])
|
|
162
|
+
|
|
163
|
+
expect(destination_exist?('app/views/shared/_header.html.erb')).to be true
|
|
164
|
+
expect(destination_exist?('app/views/shared/_bootstrap_icons.html.erb')).to be false
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it "follows --path" do
|
|
168
|
+
run_generator(generator_for("header"), %w[dark_search --path=app/views/components])
|
|
169
|
+
|
|
170
|
+
expect(destination_exist?('app/views/components/_bootstrap_icons.html.erb')).to be true
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
describe "--list" do
|
|
175
|
+
it "prints each variant with its description and writes nothing" do
|
|
176
|
+
output = run_generator(generator_for("sidebar"), %w[--list])
|
|
177
|
+
|
|
178
|
+
expect(output).to include("icon_only")
|
|
179
|
+
expect(output).to include("Narrow icon-only sidebar")
|
|
180
|
+
expect(output).to include("Default: dark")
|
|
181
|
+
expect(Dir.children(destination_root)).to be_empty
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
describe "every generator" do
|
|
186
|
+
Bootstrap::Generators::SnippetCatalog::GENERATORS.each do |generator_name, category|
|
|
187
|
+
it "bootstrap:#{generator_name} generates each of its #{category} variants" do
|
|
188
|
+
klass = generator_for(generator_name)
|
|
189
|
+
|
|
190
|
+
Catalog.variant_names(category).each do |variant|
|
|
191
|
+
remove_destination_root
|
|
192
|
+
run_generator(klass, [variant])
|
|
193
|
+
|
|
194
|
+
partial = destination_path("app/views/shared/_#{generator_name}.html.erb")
|
|
195
|
+
expect(File.exist?(partial)).to be(true), "#{generator_name} #{variant} wrote nothing"
|
|
196
|
+
expect(File.size(partial)).to be > 50
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|