"
64 | end
65 |
66 | private
67 |
68 | def uri
69 | return @uri if defined? @uri
70 |
71 | @uri = if THEME_REGEX.match?(@raw_theme)
72 | Addressable::URI.new(
73 | :scheme => "https",
74 | :host => "github.com",
75 | :path => @raw_theme
76 | )
77 | else
78 | Addressable::URI.parse @raw_theme
79 | end
80 | rescue Addressable::URI::InvalidURIError
81 | @uri = nil
82 | end
83 |
84 | def theme_parts
85 | @theme_parts ||= uri.path[1..-1].match(THEME_REGEX) if uri
86 | end
87 |
88 | def gemspec
89 | @gemspec ||= MockGemspec.new(self)
90 | end
91 |
92 | def valid_hosts
93 | @valid_hosts ||= [
94 | "github.com",
95 | ENV["PAGES_GITHUB_HOSTNAME"],
96 | ENV["GITHUB_HOSTNAME"],
97 | ].compact.to_set
98 | end
99 | end
100 | end
101 | end
102 |
--------------------------------------------------------------------------------
/lib/jekyll-remote-theme/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Jekyll
4 | module RemoteTheme
5 | VERSION = "0.4.3"
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/script/bootstrap:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | bundle install
4 |
--------------------------------------------------------------------------------
/script/cibuild:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | bundle exec rspec
6 | bundle exec rubocop
7 | gem build jekyll-remote-theme.gemspec
8 |
--------------------------------------------------------------------------------
/script/console:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | bundle exec pry -r './lib/jekyll-remote-theme.rb'
4 |
--------------------------------------------------------------------------------
/spec/fixtures/gemspecs/alldeps.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | lib = File.expand_path("lib", __dir__)
4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5 | require "alldeps/version"
6 |
7 | Gem::Specification.new do |s|
8 | s.name = "alldeps"
9 | s.version = AllDeps::VERSION
10 | s.authors = ["John Doe"]
11 | s.summary = "Dummy gemspec"
12 |
13 | # runtime dependencies
14 | s.add_dependency "jekyll", "~> 3.5"
15 | s.add_dependency "jekyll-feed", "~> 0.6"
16 | s.add_dependency "jekyll-sitemap", "~> 1.5"
17 |
18 | # development dependencies
19 | s.add_dependency "bundler", "~> 1.12"
20 | s.add_dependency "rake", "~> 10.0"
21 | end
22 |
--------------------------------------------------------------------------------
/spec/fixtures/gemspecs/braces.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | lib = File.expand_path("lib", __dir__)
4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5 | require "braces/version"
6 |
7 | Gem::Specification.new do |s|
8 | s.name = "braces"
9 | s.version = Braces::VERSION
10 | s.authors = ["John Doe"]
11 | s.summary = "Dummy gemspec"
12 |
13 | # rubocop:disable Style/StringLiterals
14 | # runtime dependencies
15 | s.add_dependency('jekyll', "~> 3.5")
16 | s.add_dependency('jekyll-feed', "~> 0.6")
17 | s.add_dependency('jekyll-sitemap', "~> 1.5")
18 |
19 | # development dependencies
20 | s.add_dependency('bundler', "~> 1.12")
21 | s.add_dependency('rake', "~> 10.0")
22 | # rubocop:enable Style/StringLiterals
23 | end
24 |
--------------------------------------------------------------------------------
/spec/fixtures/gemspecs/nodeps.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | lib = File.expand_path("lib", __dir__)
4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5 | require "nodeps/version"
6 |
7 | Gem::Specification.new do |s|
8 | s.name = "nodeps"
9 | s.version = Lorem::VERSION
10 | s.authors = ["John Doe"]
11 | s.summary = "Dummy gemspec"
12 |
13 | s.add_development_dependency("bundler", "~> 1.12")
14 | s.add_development_dependency("rake", "~> 10.0")
15 | end
16 |
--------------------------------------------------------------------------------
/spec/fixtures/gemspecs/rundev.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | lib = File.expand_path("lib", __dir__)
4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5 | require "rundev/version"
6 |
7 | Gem::Specification.new do |spec|
8 | spec.name = "rundev"
9 | spec.version = RunDev::VERSION
10 | spec.authors = ["John Doe"]
11 | spec.summary = "Dummy gemspec"
12 |
13 | spec.add_runtime_dependency "jekyll", "~> 3.5"
14 | spec.add_runtime_dependency "jekyll-feed", "~> 0.6" # some "random" comment
15 | spec.add_runtime_dependency "jekyll-sitemap", "~> 1.5"
16 |
17 | spec.add_development_dependency "bundler", "~> 1.12"
18 | spec.add_development_dependency "rake", "~> 10.0"
19 | end
20 |
--------------------------------------------------------------------------------
/spec/fixtures/site-without-theme/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
4 | # Site without theme
5 |
--------------------------------------------------------------------------------
/spec/fixtures/site/_config.yml:
--------------------------------------------------------------------------------
1 | plugins:
2 | - jekyll-remote-theme
3 |
4 | remote_theme: pages-themes/PrImeR
5 |
6 | whitelist:
7 | - jekyll-remote-theme
8 | - jekyll-seo-tag
9 | - jekyll-github-metadata
10 |
--------------------------------------------------------------------------------
/spec/fixtures/site/_malicious_config.yml:
--------------------------------------------------------------------------------
1 | plugins:
2 | - jekyll-remote-theme
3 |
4 | remote_theme: jekyll/jekyll-test-theme-malicious
5 |
6 | whitelist:
7 | - jekyll-remote-theme
8 | - jekyll-seo-tag
9 | - jekyll-github-metadata
10 |
--------------------------------------------------------------------------------
/spec/fixtures/site/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | ---
4 |
5 | # Fixture site
6 |
--------------------------------------------------------------------------------
/spec/jekyll-remote-theme/downloader_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe Jekyll::RemoteTheme::Downloader do
4 | let(:raw_theme) { "pages-themes/primer" }
5 | let(:theme) { Jekyll::RemoteTheme::Theme.new(raw_theme) }
6 | subject { described_class.new(theme) }
7 |
8 | before { reset_tmp_dir }
9 |
10 | it "knows it's not downloaded" do
11 | expect(subject.downloaded?).to be_falsy
12 | end
13 |
14 | it "creates a zip file" do
15 | expect(subject.send(:zip_file)).to be_an_existing_file
16 | end
17 |
18 | context "downloading" do
19 | before { subject.run }
20 | after { FileUtils.rm_rf theme.root if Dir.exist?(theme.root) }
21 |
22 | it "knows it's downloaded" do
23 | expect(subject.downloaded?).to be_truthy
24 | end
25 |
26 | it "extracts the theme" do
27 | expect("#{theme.root}/_layouts/default.html").to be_an_existing_file
28 | end
29 |
30 | it "deletes the zip file" do
31 | expect(subject.send(:zip_file).path).to be_nil
32 | end
33 |
34 | it "knows the theme dir exists" do
35 | expect(subject.send(:theme_dir_exists?)).to be_truthy
36 | end
37 |
38 | it "knows the theme dir isn't empty" do
39 | expect(subject.send(:theme_dir_empty?)).to be_falsy
40 | end
41 | end
42 |
43 | context "zip_url" do
44 | it "builds the zip url" do
45 | expected = "https://codeload.github.com/pages-themes/primer/zip/HEAD"
46 | expect(subject.send(:zip_url).to_s).to eql(expected)
47 | end
48 |
49 | context "a custom host" do
50 | let(:raw_theme) { "http://example.com/pages-themes/primer" }
51 |
52 | it "builds the zip url" do
53 | expected = "http://codeload.example.com/pages-themes/primer/zip/HEAD"
54 | expect(subject.send(:zip_url).to_s).to eql(expected)
55 | end
56 | end
57 | end
58 |
59 | context "with zip_url stubbed" do
60 | before { allow(subject).to receive(:zip_url) { Addressable::URI.parse zip_url } }
61 |
62 | context "with an invalid URL" do
63 | let(:zip_url) { "https://codeload.github.com/benbalter/_invalid_/zip/HEAD" }
64 | before do
65 | WebMock.disable_net_connect!
66 | stub_request(:get, zip_url).to_return(:status => [404, "Not Found"])
67 | end
68 |
69 | after { WebMock.allow_net_connect! }
70 |
71 | it "raises a DownloadError" do
72 | msg = "404 - Not Found - Loading URL: https://codeload.github.com/benbalter/_invalid_/zip/HEAD"
73 | expect { subject.run }.to raise_error(Jekyll::RemoteTheme::DownloadError, msg)
74 | end
75 | end
76 |
77 | context "with a large file" do
78 | let(:zip_url) { "https://codeload.github.com/benbalter/_invalid_/zip/HEAD" }
79 | let(:content_length) { 10 * 1024 * 1024 * 1024 }
80 | let(:headers) { { "Content-Length" => content_length } }
81 | before do
82 | WebMock.disable_net_connect!
83 | stub_request(:get, zip_url).to_return(:headers => headers)
84 | end
85 |
86 | after { WebMock.allow_net_connect! }
87 |
88 | it "raises a DownloadError" do
89 | msg = "Maximum file size of 1073741824 bytes exceeded"
90 | expect { subject.run }.to raise_error(Jekyll::RemoteTheme::DownloadError, msg)
91 | end
92 | end
93 | end
94 | end
95 |
--------------------------------------------------------------------------------
/spec/jekyll-remote-theme/integration_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe "Jekyll::RemoteTheme Integration" do
4 | attr_reader :output, :status
5 |
6 | def config_path
7 | File.join source_dir, "_config.yml"
8 | end
9 |
10 | def malicious_config_path
11 | File.join source_dir, "_malicious_config.yml"
12 | end
13 |
14 | def args(config_path)
15 | [
16 | "bundle", "exec", "jekyll", "build", "--config", config_path,
17 | "--source", source_dir, "--dest", dest_dir, "--verbose", "--safe",
18 | ]
19 | end
20 |
21 | def build_site(config_path)
22 | Dir.chdir tmp_dir do
23 | @output, @status = Open3.capture2e(*args(config_path))
24 | @output = @output.encode("UTF-8",
25 | :invalid => :replace, :undef => :replace, :replace => "")
26 | end
27 | end
28 |
29 | let(:theme) { "pages-themes/primer" }
30 | let(:index_path) { File.join dest_dir, "index.html" }
31 | let(:index_contents) { File.read(index_path) }
32 | let(:stylesheet_path) { File.join dest_dir, "assets", "css", "style.css" }
33 |
34 | context "the pages-themes/primer theme" do
35 | before(:all) { reset_tmp_dir }
36 | before(:all) { build_site(config_path) }
37 | after(:all) { reset_tmp_dir }
38 |
39 | it "returns a zero exit code" do
40 | expect(status.exitstatus).to eql(0), output
41 | end
42 |
43 | it "outputs that it's using a remote theme" do
44 | expect(output).to match("Remote Theme: Using theme #{theme}")
45 | end
46 |
47 | it "build the index" do
48 | expect(index_path).to be_an_existing_file
49 | end
50 |
51 | it "uses the theme" do
52 | expected = ''
53 | expect(index_contents).to match(expected)
54 | end
55 |
56 | it "builds stylesheets" do
57 | expect(stylesheet_path).to be_an_existing_file
58 | end
59 |
60 | it "requires dependencies" do
61 | expect(output).to include("Requiring: jekyll-seo-tag")
62 | expect(index_contents).to include("Begin Jekyll SEO tag")
63 | end
64 | end
65 |
66 | context "the jekyll/jekyll-test-theme-malicious theme" do
67 | let(:theme) { "jekyll/jekyll-test-theme-malicious" }
68 | before(:all) { reset_tmp_dir }
69 | before(:all) { build_site(malicious_config_path) }
70 | after(:all) { reset_tmp_dir }
71 |
72 | it "returns a zero exit code" do
73 | expect(status.exitstatus).to eql(0), output
74 | end
75 |
76 | it "outputs that it's using a remote theme" do
77 | expect(output).to match("Remote Theme: Using theme #{theme}")
78 | end
79 |
80 | it "build the index" do
81 | expect(index_path).to be_an_existing_file
82 | end
83 |
84 | it "uses the theme" do
85 | expect(index_contents).to include("Begin Jekyll SEO tag")
86 | end
87 |
88 | it "requires whitelisted dependencies" do
89 | expect(output).to include("Requiring: jekyll-seo-tag")
90 | end
91 |
92 | it "dosn't requires unsafe dependencies" do
93 | expect(output).to_not include("jekyll_test_plugin_malicious"), output
94 | end
95 | end
96 | end
97 |
--------------------------------------------------------------------------------
/spec/jekyll-remote-theme/mock_gemspec_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe Jekyll::RemoteTheme::MockGemspec do
4 | let(:fixture) { "alldeps" }
5 | let(:contents) { File.read gemspec_dir("#{fixture}.gemspec") }
6 | let(:filename) { "#{theme.name}.gemspec" }
7 | let(:path) { File.expand_path filename, theme.root }
8 | let(:nwo) { "pages-themes/primer" }
9 | let(:theme) { Jekyll::RemoteTheme::Theme.new(nwo) }
10 | subject { described_class.new(theme) }
11 |
12 | before { File.write path, contents }
13 |
14 | it "stores the theme" do
15 | expect(subject.send(:theme)).to eql(theme)
16 | end
17 |
18 | it "determines the path" do
19 | expect(subject.send(:path)).to eql(path)
20 | end
21 |
22 | it "reads the contents" do
23 | expect(subject.send(:contents)).to eql(contents)
24 | end
25 |
26 | it "builds potential_paths" do
27 | expect(subject.send(:potential_paths)).to include(path)
28 | end
29 |
30 | it "returns the theme root" do
31 | expect(subject.full_gem_path).to eql(theme.root)
32 | end
33 |
34 | context "fixtures" do
35 | let(:dependency_names) { subject.send(:dependency_names) }
36 | let(:runtime_dependencies) { subject.runtime_dependencies }
37 |
38 | # Hash in the form of gemspec fixture => expected dependencies
39 | {
40 | "alldeps" => %w(jekyll jekyll-feed jekyll-sitemap bundler rake),
41 | "braces" => %w(jekyll jekyll-feed jekyll-sitemap bundler rake),
42 | "rundev" => %w(jekyll jekyll-feed jekyll-sitemap),
43 | "nodeps" => [],
44 | }.each do |fixture, expected|
45 | context "the #{fixture} gemspec" do
46 | let(:fixture) { fixture }
47 |
48 | it "returns dependency names" do
49 | expect(dependency_names).to eql(expected)
50 | end
51 |
52 | it "returns #{expected.count} runtime dependencies" do
53 | expect(runtime_dependencies.count).to eql(expected.count)
54 |
55 | unless expected.empty?
56 | expect(runtime_dependencies.first).to be_a(Gem::Dependency)
57 | expect(runtime_dependencies.map(&:name)).to eql(expected)
58 | end
59 | end
60 | end
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/spec/jekyll-remote-theme/munger_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe Jekyll::RemoteTheme::Munger do
4 | let(:source) { source_dir }
5 | let(:overrides) { {} }
6 | let(:config) { { "source" => source, "safe" => true }.merge(overrides) }
7 | let(:site) { make_site(config) }
8 | let(:theme_dir) { theme&.root }
9 | let(:layout_path) { File.expand_path "_layouts/default.html", theme_dir }
10 | let(:sass_dir) { File.expand_path "_sass/", theme_dir }
11 | let(:sass_path) { File.expand_path "jekyll-theme-primer.scss", sass_dir }
12 | let(:includes_dir) { File.expand_path "_includes/", theme_dir }
13 | let(:theme) { subject.send(:theme) }
14 |
15 | subject { described_class.new(site) }
16 |
17 | before { Jekyll.logger.log_level = :error }
18 | before { reset_tmp_dir }
19 |
20 | # Remove :after_reset hook to allow themes to be stubbed prior to munging
21 | before(:each) do
22 | hooks = Jekyll::Hooks.instance_variable_get("@registry")
23 | hooks[:site][:after_reset] = []
24 | Jekyll::Hooks.instance_variable_set("@registry", hooks)
25 | end
26 |
27 | it "stores the site" do
28 | expect(subject.site).to be_a(Jekyll::Site)
29 | end
30 |
31 | context "without a theme" do
32 | let(:source) { fixture_path("site-without-theme") }
33 |
34 | it "doesn't set a theme" do
35 | expect(site.theme).to_not be_a(Jekyll::RemoteTheme::Theme)
36 | end
37 |
38 | it "doesn't clone" do
39 | expect(layout_path).to_not be_an_existing_file
40 | end
41 | end
42 |
43 | context "with theme as a hash" do
44 | let(:overrides) { { "remote_theme" => { "foo" => "bar" } } }
45 | before { subject.munge! }
46 |
47 | it "doesn't set a theme" do
48 | expect(site.theme).to_not be_a(Jekyll::RemoteTheme::Theme)
49 | end
50 |
51 | it "doesn't clone" do
52 | expect(layout_path).to_not be_an_existing_file
53 | end
54 | end
55 |
56 | context "with a remote theme" do
57 | let(:overrides) { { "remote_theme" => "pages-themes/primer" } }
58 | before do
59 | @old_logger = Jekyll.logger
60 | @stubbed_logger = StringIO.new
61 | Jekyll.logger = Logger.new(@stubbed_logger)
62 | Jekyll.logger.log_level = :debug
63 | end
64 | before { subject.munge! }
65 | after { Jekyll.instance_variable_set("@logger", @old_logger) }
66 |
67 | it "sets the theme" do
68 | expect(site.theme).to be_a(Jekyll::RemoteTheme::Theme)
69 | expect(site.theme.name).to eql("primer")
70 | expect(site.config["theme"]).to eql("primer")
71 | end
72 |
73 | it "downloads" do
74 | expect(layout_path).to be_an_existing_file
75 | end
76 |
77 | it "sets sass paths" do
78 | expect(sass_path).to be_an_existing_file
79 |
80 | if Jekyll::VERSION >= "4.0"
81 | converter = Jekyll::Converters::Scss.new(site.config)
82 |
83 | expect(converter.sass_configs[:load_paths]).to include(sass_dir)
84 | else
85 | expect(Sass.load_paths).to include(sass_dir)
86 | end
87 | end
88 |
89 | it "sets include paths" do
90 | expect(site.includes_load_paths).to include(includes_dir)
91 | end
92 |
93 | it "sets layouts" do
94 | site.read
95 | expect(site.layouts["default"]).to be_truthy
96 | expect(site.layouts["default"].path).to eql(layout_path)
97 | end
98 |
99 | it "requires plugins" do
100 | @stubbed_logger.rewind
101 | expect(@stubbed_logger.read).to include("Requiring: jekyll-seo-tag")
102 | end
103 | end
104 |
105 | context "with a malicious theme" do
106 | let(:overrides) { { "remote_theme" => "jekyll/jekyll-test-theme-malicious" } }
107 | before do
108 | @old_logger = Jekyll.logger
109 | @stubbed_logger = StringIO.new
110 | Jekyll.logger = Logger.new(@stubbed_logger)
111 | Jekyll.logger.log_level = :debug
112 | end
113 | before { subject.munge! }
114 | after { Jekyll.instance_variable_set("@logger", @old_logger) }
115 |
116 | it "sets the theme" do
117 | expect(site.theme).to be_a(Jekyll::RemoteTheme::Theme)
118 | expect(site.theme.name).to eql("jekyll-test-theme-malicious")
119 | expect(site.config["theme"]).to eql("jekyll-test-theme-malicious")
120 | end
121 |
122 | it "requires whitelisted plugins" do
123 | @stubbed_logger.rewind
124 | expect(@stubbed_logger.read).to include("Requiring: jekyll-seo-tag")
125 | end
126 |
127 | it "doesn't require malicious plugins" do
128 | @stubbed_logger.rewind
129 | expect(@stubbed_logger.read).to_not include("jekyll_test_plugin_malicious")
130 | end
131 | end
132 | end
133 |
--------------------------------------------------------------------------------
/spec/jekyll-remote-theme/theme_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe Jekyll::RemoteTheme::Theme do
4 | let(:scheme) { nil }
5 | let(:host) { nil }
6 | let(:owner) { "foo" }
7 | let(:name) { "bar" }
8 | let(:nwo) { "#{owner}/#{name}" }
9 | let(:git_ref) { nil }
10 | let(:raw_theme) do
11 | raw_theme = +""
12 | raw_theme << "#{scheme}://#{host}/" if scheme && host
13 | raw_theme << nwo.to_s
14 | raw_theme << "@#{git_ref}" if git_ref
15 | raw_theme
16 | end
17 | subject { described_class.new(raw_theme) }
18 |
19 | it "stores the theme" do
20 | expect(subject.instance_variable_get("@raw_theme")).to eql(nwo)
21 | end
22 |
23 | context "with an abnormal NWO" do
24 | let(:nwo) { " FoO/bAr " }
25 |
26 | it "normalizes the nwo" do
27 | expect(subject.instance_variable_get("@raw_theme")).to eql("foo/bar")
28 | end
29 | end
30 |
31 | it "extracts the name" do
32 | expect(subject.name).to eql(name)
33 | end
34 |
35 | it "extracts the owner" do
36 | expect(subject.owner).to eql(owner)
37 | end
38 |
39 | it "uses the default host" do
40 | expect(subject.host).to eql("github.com")
41 | end
42 |
43 | it "uses the default scheme" do
44 | expect(subject.scheme).to eql("https")
45 | end
46 |
47 | it "builds the name with owner" do
48 | expect(subject.name_with_owner).to eql(nwo)
49 | expect(subject.nwo).to eql(nwo)
50 | end
51 |
52 | it "knows it's valid" do
53 | expect(subject).to be_valid
54 | end
55 |
56 | context "a random string" do
57 | let(:nwo) { "foo" }
58 |
59 | it "isn't valid" do
60 | expect(subject).to_not be_valid
61 | end
62 | end
63 |
64 | context "with a non-string" do
65 | let(:nwo) { [1, 2] }
66 |
67 | it "isn't valid" do
68 | expect(subject).to_not be_valid
69 | end
70 | end
71 |
72 | context "with a non-nwo string" do
73 | let(:nwo) { "foo/javascript: alert(1);" }
74 |
75 | it "isn't valid" do
76 | expect(subject).to_not be_valid
77 | end
78 | end
79 |
80 | it "defaults git_ref to HEAD" do
81 | expect(subject.git_ref).to eql("HEAD")
82 | end
83 |
84 | context "with a git_ref" do
85 | let(:git_ref) { "foo" }
86 |
87 | it "parses the git ref" do
88 | expect(subject.git_ref).to eql(git_ref)
89 | end
90 | end
91 |
92 | it "knows its root" do
93 | expect(Dir.exist?(subject.root)).to be_truthy
94 | end
95 |
96 | it "exposes gemspec" do
97 | expect(subject.send(:gemspec)).to be_a(Jekyll::RemoteTheme::MockGemspec)
98 | end
99 |
100 | context "a full URL" do
101 | let(:host) { "github.com" }
102 | let(:scheme) { "https" }
103 |
104 | it "extracts the name" do
105 | expect(subject.name).to eql(name)
106 | end
107 |
108 | it "extracts the owner" do
109 | expect(subject.owner).to eql(owner)
110 | end
111 |
112 | it "extracts the host" do
113 | expect(subject.host).to eql("github.com")
114 | end
115 |
116 | it "extracts the scheme" do
117 | expect(subject.scheme).to eql("https")
118 | end
119 |
120 | it "is valid" do
121 | with_env "GITHUB_HOSTNAME", "enterprise.github.com" do
122 | expect(subject).to be_valid
123 | end
124 | end
125 |
126 | context "a custom host" do
127 | let(:host) { "example.com" }
128 | let(:scheme) { "http" }
129 |
130 | it "extracts the name" do
131 | expect(subject.name).to eql(name)
132 | end
133 |
134 | it "extracts the owner" do
135 | expect(subject.owner).to eql(owner)
136 | end
137 |
138 | it "extracts the host" do
139 | expect(subject.host).to eql(host)
140 | end
141 |
142 | it "extracts the scheme" do
143 | expect(subject.scheme).to eql(scheme)
144 | end
145 |
146 | it "is valid if a whitelisted host name" do
147 | with_env "GITHUB_HOSTNAME", "example.com" do
148 | expect(subject).to be_valid
149 | end
150 | end
151 |
152 | it "is invalid if not a whitelisted host name" do
153 | with_env "GITHUB_HOSTNAME", "enterprise.github.com" do
154 | expect(subject).to_not be_valid
155 | end
156 | end
157 |
158 | context "with a git ref" do
159 | let(:git_ref) { "foo" }
160 |
161 | it "parses the git ref" do
162 | expect(subject.git_ref).to eql(git_ref)
163 | end
164 | end
165 | end
166 | end
167 | end
168 |
--------------------------------------------------------------------------------
/spec/jekyll_remote_theme_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe Jekyll::RemoteTheme do
4 | let(:source) { source_dir }
5 | let(:config) { { "source" => source } }
6 | let(:site) { make_site(config) }
7 | subject { described_class }
8 |
9 | it "returns the version" do
10 | expect(subject::VERSION).to match(%r!\d+\.\d+\.\d+!)
11 | end
12 |
13 | it "inits" do
14 | expect(subject.init(site)).to be_a(Jekyll::RemoteTheme::Theme)
15 | expect(site.theme).to be_a(Jekyll::RemoteTheme::Theme)
16 | expect(File.join(site.theme.root, "_layouts/default.html")).to be_an_existing_file
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative "../lib/jekyll-remote-theme"
4 | require "fileutils"
5 | require "open3"
6 | require "pathname"
7 | require "webmock/rspec"
8 | WebMock.allow_net_connect!
9 |
10 | RSpec.configure do |config|
11 | config.example_status_persistence_file_path = "spec/examples.txt"
12 | config.disable_monkey_patching!
13 | config.warnings = true
14 | config.default_formatter = "doc" if config.files_to_run.one?
15 | config.order = :random
16 | Kernel.srand config.seed
17 | end
18 |
19 | RSpec::Matchers.define :be_an_existing_file do
20 | match { |path| File.exist?(path) }
21 | end
22 |
23 | def tmp_dir
24 | @tmp_dir ||= File.expand_path "../tmp", __dir__
25 | end
26 |
27 | def source_dir
28 | @source_dir ||= fixture_path "site"
29 | end
30 |
31 | def dest_dir
32 | @dest_dir ||= File.join tmp_dir, "dest"
33 | end
34 |
35 | def gemspec_dir(*contents)
36 | File.join(fixture_path("gemspecs"), *contents)
37 | end
38 |
39 | def reset_tmp_dir
40 | FileUtils.rm_rf tmp_dir
41 | FileUtils.mkdir_p tmp_dir
42 | end
43 |
44 | def fixture_path(fixture)
45 | File.expand_path "fixtures/#{fixture}", __dir__
46 | end
47 |
48 | def config_defaults
49 | {
50 | "source" => source_dir,
51 | "destination" => dest_dir,
52 | "gems" => ["jekyll-remote-theme"],
53 | }
54 | end
55 |
56 | def make_site(options = {})
57 | config = Jekyll.configuration config_defaults.merge(options)
58 | Jekyll::Site.new(config)
59 | end
60 |
61 | def with_env(key, value)
62 | old_env = ENV[key]
63 | ENV[key] = value
64 | yield
65 | ENV[key] = old_env
66 | end
67 |
--------------------------------------------------------------------------------