diff --git a/Gemfile b/Gemfile
index d978e31..43357d1 100644
--- a/Gemfile
+++ b/Gemfile
@@ -12,8 +12,7 @@ gem "bake", "~> 0.20"
gem "nokogiri", "~> 1.18"
group :development, :test do
- gem "rspec", "~> 3.13"
gem "guard", "~> 2.18"
- gem "guard-rspec", "~> 4.7"
+ gem "minitest", "~> 6.0"
gem "standard", "~> 1.43"
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 9f0d135..e03c171 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -13,7 +13,6 @@ GEM
fiber-annotation
fiber-local (~> 1.1)
json
- diff-lcs (1.6.2)
dry-core (1.2.0)
concurrent-ruby (~> 1.0)
logger
@@ -63,11 +62,6 @@ GEM
pry (>= 0.13.0)
shellany (~> 0.0)
thor (>= 0.18.1)
- guard-compat (1.2.1)
- guard-rspec (4.7.3)
- guard (~> 2.1)
- guard-compat (~> 1.1)
- rspec (>= 2.99.0, < 4.0)
ice_nine (0.11.2)
io-console (0.8.2)
json (2.18.1)
@@ -86,6 +80,8 @@ GEM
mapping (1.1.3)
method_source (1.1.0)
mini_portile2 (2.8.9)
+ minitest (6.0.0)
+ prism (~> 1.5)
nenv (0.3.0)
nokogiri (1.19.0)
mini_portile2 (~> 2.8.2)
@@ -134,19 +130,6 @@ GEM
io-console (~> 0.5)
rexml (3.4.4)
rouge (4.7.0)
- rspec (3.13.2)
- rspec-core (~> 3.13.0)
- rspec-expectations (~> 3.13.0)
- rspec-mocks (~> 3.13.0)
- rspec-core (3.13.6)
- rspec-support (~> 3.13.0)
- rspec-expectations (3.13.5)
- diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.13.0)
- rspec-mocks (3.13.7)
- diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.13.0)
- rspec-support (3.13.7)
rubocop (1.82.1)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
@@ -194,7 +177,6 @@ PLATFORMS
arm-linux-gnu
arm-linux-musl
arm64-darwin
- ruby
x86-linux-gnu
x86-linux-musl
x86_64-darwin
@@ -206,13 +188,12 @@ DEPENDENCIES
builder (~> 3.3)
dry-struct (~> 1.8)
guard (~> 2.18)
- guard-rspec (~> 4.7)
kramdown (~> 2.5)
kramdown-parser-gfm (~> 1.1)
+ minitest (~> 6.0)
nokogiri (~> 1.18)
phlex (~> 2.3)
rouge (~> 4.6)
- rspec (~> 3.13)
standard (~> 1.43)
RUBY VERSION
diff --git a/Readme.md b/Readme.md
index d122e50..3216e89 100644
--- a/Readme.md
+++ b/Readme.md
@@ -78,7 +78,7 @@ Published posts in `posts/YYYY/MM/*.md` require YAML front matter keys:
## Tests And Lint
```bash
-rbenv exec bundle exec rspec
+rbenv exec bundle exec bake test
rbenv exec bundle exec standardrb
```
diff --git a/bake.rb b/bake.rb
index 0400276..8e7b372 100644
--- a/bake.rb
+++ b/bake.rb
@@ -136,9 +136,12 @@ def clean
puts "Cleaned www/ directory"
end
-# Run RSpec tests
+# Run Minitest tests
def test
- exec "bundle exec rspec"
+ test_files = Dir.glob("spec/**/*_test.rb").sort
+ abort "Error: no tests found in spec/**/*_test.rb" if test_files.empty?
+
+ exec "ruby", "-Ilib", "-Ispec", "-e", "ARGV.each { |file| require File.expand_path(file) }", *test_files
end
# Run Guard for continuous testing
diff --git a/spec/config/loader_spec.rb b/spec/config/loader_spec.rb
deleted file mode 100644
index 396f055..0000000
--- a/spec/config/loader_spec.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-require "spec_helper"
-require "fileutils"
-require "tmpdir"
-
-RSpec.describe Pressa::Config::Loader do
- describe "#build_site" do
- it "builds a site from site.toml and projects.toml" do
- with_temp_config do |dir|
- loader = described_class.new(source_path: dir)
- site = loader.build_site
-
- expect(site.author).to eq("Sami Samhuri")
- expect(site.url).to eq("https://samhuri.net")
- expect(site.image_url).to eq("https://samhuri.net/images/me.jpg")
- expect(site.styles.map(&:href)).to eq(["css/style.css"])
-
- projects_plugin = site.plugins.find { |plugin| plugin.is_a?(Pressa::Projects::Plugin) }
- expect(projects_plugin).not_to be_nil
- expect(projects_plugin.scripts.map(&:src)).to eq(["js/projects.js"])
- end
- end
-
- it "applies url_override and rewrites relative image_url with override host" do
- with_temp_config do |dir|
- loader = described_class.new(source_path: dir)
- site = loader.build_site(url_override: "https://beta.samhuri.net")
-
- expect(site.url).to eq("https://beta.samhuri.net")
- expect(site.image_url).to eq("https://beta.samhuri.net/images/me.jpg")
- end
- end
-
- it "raises a validation error for missing required site keys" do
- Dir.mktmpdir do |dir|
- File.write(File.join(dir, "site.toml"), "title = \"x\"\n")
- File.write(File.join(dir, "projects.toml"), "")
-
- loader = described_class.new(source_path: dir)
- expect { loader.build_site }.to raise_error(Pressa::Config::ValidationError, /Missing required site\.toml keys/)
- end
- end
- end
-
- def with_temp_config
- Dir.mktmpdir do |dir|
- File.write(File.join(dir, "site.toml"), <<~TOML)
- author = "Sami Samhuri"
- email = "sami@samhuri.net"
- title = "samhuri.net"
- description = "blog"
- url = "https://samhuri.net"
- image_url = "/images/me.jpg"
- scripts = []
- styles = ["css/style.css"]
-
- [projects_plugin]
- scripts = ["js/projects.js"]
- styles = []
- TOML
-
- File.write(File.join(dir, "projects.toml"), <<~TOML)
- [[projects]]
- name = "demo"
- title = "demo"
- description = "demo project"
- url = "https://github.com/samsonjs/demo"
- TOML
-
- yield dir
- end
- end
-end
diff --git a/spec/config/loader_test.rb b/spec/config/loader_test.rb
new file mode 100644
index 0000000..640af06
--- /dev/null
+++ b/spec/config/loader_test.rb
@@ -0,0 +1,73 @@
+require "test_helper"
+require "fileutils"
+require "tmpdir"
+
+class Pressa::Config::LoaderTest < Minitest::Test
+ def test_build_site_builds_a_site_from_site_toml_and_projects_toml
+ with_temp_config do |dir|
+ loader = Pressa::Config::Loader.new(source_path: dir)
+ site = loader.build_site
+
+ assert_equal("Sami Samhuri", site.author)
+ assert_equal("https://samhuri.net", site.url)
+ assert_equal("https://samhuri.net/images/me.jpg", site.image_url)
+ assert_equal(["css/style.css"], site.styles.map(&:href))
+
+ projects_plugin = site.plugins.find { |plugin| plugin.is_a?(Pressa::Projects::Plugin) }
+ refute_nil(projects_plugin)
+ assert_equal(["js/projects.js"], projects_plugin.scripts.map(&:src))
+ end
+ end
+
+ def test_build_site_applies_url_override_and_rewrites_relative_image_url_with_override_host
+ with_temp_config do |dir|
+ loader = Pressa::Config::Loader.new(source_path: dir)
+ site = loader.build_site(url_override: "https://beta.samhuri.net")
+
+ assert_equal("https://beta.samhuri.net", site.url)
+ assert_equal("https://beta.samhuri.net/images/me.jpg", site.image_url)
+ end
+ end
+
+ def test_build_site_raises_a_validation_error_for_missing_required_site_keys
+ Dir.mktmpdir do |dir|
+ File.write(File.join(dir, "site.toml"), "title = \"x\"\n")
+ File.write(File.join(dir, "projects.toml"), "")
+
+ loader = Pressa::Config::Loader.new(source_path: dir)
+ error = assert_raises(Pressa::Config::ValidationError) { loader.build_site }
+ assert_match(/Missing required site\.toml keys/, error.message)
+ end
+ end
+
+ private
+
+ def with_temp_config
+ Dir.mktmpdir do |dir|
+ File.write(File.join(dir, "site.toml"), <<~TOML)
+ author = "Sami Samhuri"
+ email = "sami@samhuri.net"
+ title = "samhuri.net"
+ description = "blog"
+ url = "https://samhuri.net"
+ image_url = "/images/me.jpg"
+ scripts = []
+ styles = ["css/style.css"]
+
+ [projects_plugin]
+ scripts = ["js/projects.js"]
+ styles = []
+ TOML
+
+ File.write(File.join(dir, "projects.toml"), <<~TOML)
+ [[projects]]
+ name = "demo"
+ title = "demo"
+ description = "demo project"
+ url = "https://github.com/samsonjs/demo"
+ TOML
+
+ yield dir
+ end
+ end
+end
diff --git a/spec/posts/json_feed_spec.rb b/spec/posts/json_feed_spec.rb
deleted file mode 100644
index 70f249b..0000000
--- a/spec/posts/json_feed_spec.rb
+++ /dev/null
@@ -1,100 +0,0 @@
-require "spec_helper"
-require "json"
-require "tmpdir"
-
-RSpec.describe Pressa::Posts::JSONFeedWriter do
- let(:site) do
- Pressa::Site.new(
- author: "Sami Samhuri",
- email: "sami@samhuri.net",
- title: "samhuri.net",
- description: "blog",
- url: "https://samhuri.net",
- image_url: "https://samhuri.net/images/me.jpg"
- )
- end
-
- let(:posts_by_year) { double("posts_by_year", recent_posts: [post]) }
- let(:writer) { described_class.new(site:, posts_by_year:) }
-
- context "for link posts" do
- let(:post) do
- Pressa::Posts::Post.new(
- slug: "github-flow-like-a-pro",
- title: "GitHub Flow Like a Pro",
- author: "Sami Samhuri",
- date: DateTime.parse("2015-05-28T07:42:27-07:00"),
- formatted_date: "28th May, 2015",
- link: "http://haacked.com/archive/2014/07/28/github-flow-aliases/",
- body: "
hello
",
- excerpt: "hello...",
- path: "/posts/2015/05/github-flow-like-a-pro"
- )
- end
-
- it "uses permalink as url and keeps external_url for destination links" do
- Dir.mktmpdir do |dir|
- writer.write_feed(target_path: dir, limit: 30)
- feed = JSON.parse(File.read(File.join(dir, "feed.json")))
- item = feed.fetch("items").first
-
- expect(item.fetch("id")).to eq("https://samhuri.net/posts/2015/05/github-flow-like-a-pro")
- expect(item.fetch("url")).to eq("https://samhuri.net/posts/2015/05/github-flow-like-a-pro")
- expect(item.fetch("external_url")).to eq("http://haacked.com/archive/2014/07/28/github-flow-aliases/")
- end
- end
- end
-
- context "for regular posts" do
- let(:post) do
- Pressa::Posts::Post.new(
- slug: "swift-optional-or",
- title: "Swift Optional OR",
- author: "Sami Samhuri",
- date: DateTime.parse("2017-10-01T10:00:00-07:00"),
- formatted_date: "1st October, 2017",
- body: "hello
",
- excerpt: "hello...",
- path: "/posts/2017/10/swift-optional-or"
- )
- end
-
- it "omits external_url" do
- Dir.mktmpdir do |dir|
- writer.write_feed(target_path: dir, limit: 30)
- feed = JSON.parse(File.read(File.join(dir, "feed.json")))
- item = feed.fetch("items").first
-
- expect(item.fetch("url")).to eq("https://samhuri.net/posts/2017/10/swift-optional-or")
- expect(item).not_to have_key("external_url")
- end
- end
-
- it "expands root-relative links in content_html to absolute URLs" do
- post_with_assets = Pressa::Posts::Post.new(
- slug: "swift-optional-or",
- title: "Swift Optional OR",
- author: "Sami Samhuri",
- date: DateTime.parse("2017-10-01T10:00:00-07:00"),
- formatted_date: "1st October, 2017",
- body: 'read
' \
- '
' \
- 'cdn
',
- excerpt: "hello...",
- path: "/posts/2017/10/swift-optional-or"
- )
- allow(posts_by_year).to receive(:recent_posts).and_return([post_with_assets])
-
- Dir.mktmpdir do |dir|
- writer.write_feed(target_path: dir, limit: 30)
- feed = JSON.parse(File.read(File.join(dir, "feed.json")))
- item = feed.fetch("items").first
- content_html = item.fetch("content_html")
-
- expect(content_html).to include('href="https://samhuri.net/posts/2010/01/basics-of-the-mach-o-file-format"')
- expect(content_html).to include('src="https://samhuri.net/images/me.jpg"')
- expect(content_html).to include('href="//cdn.example.net/app.js"')
- end
- end
- end
-end
diff --git a/spec/posts/json_feed_test.rb b/spec/posts/json_feed_test.rb
new file mode 100644
index 0000000..6325432
--- /dev/null
+++ b/spec/posts/json_feed_test.rb
@@ -0,0 +1,115 @@
+require "test_helper"
+require "json"
+require "tmpdir"
+
+class Pressa::Posts::JSONFeedWriterTest < Minitest::Test
+ class PostsByYearStub
+ attr_accessor :posts
+
+ def initialize(posts)
+ @posts = posts
+ end
+
+ def recent_posts(_limit = 30)
+ @posts
+ end
+ end
+
+ def setup
+ @site = Pressa::Site.new(
+ author: "Sami Samhuri",
+ email: "sami@samhuri.net",
+ title: "samhuri.net",
+ description: "blog",
+ url: "https://samhuri.net",
+ image_url: "https://samhuri.net/images/me.jpg"
+ )
+
+ @posts_by_year = PostsByYearStub.new([link_post])
+ @writer = Pressa::Posts::JSONFeedWriter.new(site: @site, posts_by_year: @posts_by_year)
+ end
+
+ def test_write_feed_for_link_posts_uses_permalink_as_url_and_keeps_external_url
+ Dir.mktmpdir do |dir|
+ @writer.write_feed(target_path: dir, limit: 30)
+ feed = JSON.parse(File.read(File.join(dir, "feed.json")))
+ item = feed.fetch("items").first
+
+ assert_equal("https://samhuri.net/posts/2015/05/github-flow-like-a-pro", item.fetch("id"))
+ assert_equal("https://samhuri.net/posts/2015/05/github-flow-like-a-pro", item.fetch("url"))
+ assert_equal("http://haacked.com/archive/2014/07/28/github-flow-aliases/", item.fetch("external_url"))
+ end
+ end
+
+ def test_write_feed_for_regular_posts_omits_external_url
+ @posts_by_year.posts = [regular_post]
+
+ Dir.mktmpdir do |dir|
+ @writer.write_feed(target_path: dir, limit: 30)
+ feed = JSON.parse(File.read(File.join(dir, "feed.json")))
+ item = feed.fetch("items").first
+
+ assert_equal("https://samhuri.net/posts/2017/10/swift-optional-or", item.fetch("url"))
+ refute(item.key?("external_url"))
+ end
+ end
+
+ def test_write_feed_expands_root_relative_links_in_content_html
+ @posts_by_year.posts = [post_with_assets]
+
+ Dir.mktmpdir do |dir|
+ @writer.write_feed(target_path: dir, limit: 30)
+ feed = JSON.parse(File.read(File.join(dir, "feed.json")))
+ item = feed.fetch("items").first
+ content_html = item.fetch("content_html")
+
+ assert_includes(content_html, 'href="https://samhuri.net/posts/2010/01/basics-of-the-mach-o-file-format"')
+ assert_includes(content_html, 'src="https://samhuri.net/images/me.jpg"')
+ assert_includes(content_html, 'href="//cdn.example.net/app.js"')
+ end
+ end
+
+ private
+
+ def link_post
+ Pressa::Posts::Post.new(
+ slug: "github-flow-like-a-pro",
+ title: "GitHub Flow Like a Pro",
+ author: "Sami Samhuri",
+ date: DateTime.parse("2015-05-28T07:42:27-07:00"),
+ formatted_date: "28th May, 2015",
+ link: "http://haacked.com/archive/2014/07/28/github-flow-aliases/",
+ body: "hello
",
+ excerpt: "hello...",
+ path: "/posts/2015/05/github-flow-like-a-pro"
+ )
+ end
+
+ def regular_post
+ Pressa::Posts::Post.new(
+ slug: "swift-optional-or",
+ title: "Swift Optional OR",
+ author: "Sami Samhuri",
+ date: DateTime.parse("2017-10-01T10:00:00-07:00"),
+ formatted_date: "1st October, 2017",
+ body: "hello
",
+ excerpt: "hello...",
+ path: "/posts/2017/10/swift-optional-or"
+ )
+ end
+
+ def post_with_assets
+ Pressa::Posts::Post.new(
+ slug: "swift-optional-or",
+ title: "Swift Optional OR",
+ author: "Sami Samhuri",
+ date: DateTime.parse("2017-10-01T10:00:00-07:00"),
+ formatted_date: "1st October, 2017",
+ body: 'read
' \
+ '
' \
+ 'cdn
',
+ excerpt: "hello...",
+ path: "/posts/2017/10/swift-optional-or"
+ )
+ end
+end
diff --git a/spec/posts/metadata_spec.rb b/spec/posts/metadata_spec.rb
deleted file mode 100644
index 831f212..0000000
--- a/spec/posts/metadata_spec.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-require "spec_helper"
-
-RSpec.describe Pressa::Posts::PostMetadata do
- describe ".parse" do
- it "parses valid YAML front-matter" do
- content = <<~MARKDOWN
- ---
- Title: Test Post
- Author: Trent Reznor
- Date: 5th November, 2025
- Timestamp: 2025-11-05T10:00:00-08:00
- Tags: Ruby, Testing
- Scripts: highlight.js
- Styles: code.css
- Link: https://example.net/external
- ---
-
- This is the post body.
- MARKDOWN
-
- metadata = described_class.parse(content)
-
- expect(metadata.title).to eq("Test Post")
- expect(metadata.author).to eq("Trent Reznor")
- expect(metadata.formatted_date).to eq("5th November, 2025")
- expect(metadata.date.year).to eq(2025)
- expect(metadata.date.month).to eq(11)
- expect(metadata.date.day).to eq(5)
- expect(metadata.link).to eq("https://example.net/external")
- expect(metadata.tags).to eq(["Ruby", "Testing"])
- expect(metadata.scripts.map(&:src)).to eq(["js/highlight.js"])
- expect(metadata.styles.map(&:href)).to eq(["css/code.css"])
- end
-
- it "raises error when required fields are missing" do
- content = <<~MARKDOWN
- ---
- Title: Incomplete Post
- ---
-
- Body content
- MARKDOWN
-
- expect {
- described_class.parse(content)
- }.to raise_error(/Missing required fields/)
- end
-
- it "handles posts without optional fields" do
- content = <<~MARKDOWN
- ---
- Title: Simple Post
- Author: Fat Mike
- Date: 1st January, 2025
- Timestamp: 2025-01-01T12:00:00-08:00
- ---
-
- Simple content
- MARKDOWN
-
- metadata = described_class.parse(content)
-
- expect(metadata.tags).to eq([])
- expect(metadata.scripts).to eq([])
- expect(metadata.styles).to eq([])
- expect(metadata.link).to be_nil
- end
- end
-end
diff --git a/spec/posts/metadata_test.rb b/spec/posts/metadata_test.rb
new file mode 100644
index 0000000..45675ca
--- /dev/null
+++ b/spec/posts/metadata_test.rb
@@ -0,0 +1,66 @@
+require "test_helper"
+
+class Pressa::Posts::PostMetadataTest < Minitest::Test
+ def test_parse_parses_valid_yaml_front_matter
+ content = <<~MARKDOWN
+ ---
+ Title: Test Post
+ Author: Trent Reznor
+ Date: 5th November, 2025
+ Timestamp: 2025-11-05T10:00:00-08:00
+ Tags: Ruby, Testing
+ Scripts: highlight.js
+ Styles: code.css
+ Link: https://example.net/external
+ ---
+
+ This is the post body.
+ MARKDOWN
+
+ metadata = Pressa::Posts::PostMetadata.parse(content)
+
+ assert_equal("Test Post", metadata.title)
+ assert_equal("Trent Reznor", metadata.author)
+ assert_equal("5th November, 2025", metadata.formatted_date)
+ assert_equal(2025, metadata.date.year)
+ assert_equal(11, metadata.date.month)
+ assert_equal(5, metadata.date.day)
+ assert_equal("https://example.net/external", metadata.link)
+ assert_equal(["Ruby", "Testing"], metadata.tags)
+ assert_equal(["js/highlight.js"], metadata.scripts.map(&:src))
+ assert_equal(["css/code.css"], metadata.styles.map(&:href))
+ end
+
+ def test_parse_raises_error_when_required_fields_are_missing
+ content = <<~MARKDOWN
+ ---
+ Title: Incomplete Post
+ ---
+
+ Body content
+ MARKDOWN
+
+ error = assert_raises(StandardError) { Pressa::Posts::PostMetadata.parse(content) }
+ assert_match(/Missing required fields/, error.message)
+ end
+
+ def test_parse_handles_posts_without_optional_fields
+ content = <<~MARKDOWN
+ ---
+ Title: Simple Post
+ Author: Fat Mike
+ Date: 1st January, 2025
+ Timestamp: 2025-01-01T12:00:00-08:00
+ ---
+
+ Simple content
+ MARKDOWN
+
+ metadata = Pressa::Posts::PostMetadata.parse(content)
+
+ assert_equal([], metadata.tags)
+ assert_equal([], metadata.scripts)
+ assert_equal([], metadata.styles)
+ assert_nil(metadata.link)
+ end
+end
diff --git a/spec/posts/repo_spec.rb b/spec/posts/repo_spec.rb
deleted file mode 100644
index abd9030..0000000
--- a/spec/posts/repo_spec.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-require "spec_helper"
-require "fileutils"
-require "tmpdir"
-
-RSpec.describe Pressa::Posts::PostRepo do
- let(:repo) { described_class.new }
-
- describe "#read_posts" do
- it "reads and organizes posts by year and month" do
- Dir.mktmpdir do |tmpdir|
- posts_dir = File.join(tmpdir, "posts", "2025", "11")
- FileUtils.mkdir_p(posts_dir)
-
- post_content = <<~MARKDOWN
- ---
- Title: Shredding in November
- Author: Shaun White
- Date: 5th November, 2025
- Timestamp: 2025-11-05T10:00:00-08:00
- ---
-
- Had an epic day at Whistler. The powder was deep and the lines were short.
- MARKDOWN
-
- File.write(File.join(posts_dir, "shredding.md"), post_content)
-
- posts_by_year = repo.read_posts(File.join(tmpdir, "posts"))
-
- expect(posts_by_year.all_posts.length).to eq(1)
-
- post = posts_by_year.all_posts.first
- expect(post.title).to eq("Shredding in November")
- expect(post.author).to eq("Shaun White")
- expect(post.slug).to eq("shredding")
- expect(post.year).to eq(2025)
- expect(post.month).to eq(11)
- expect(post.path).to eq("/posts/2025/11/shredding")
- end
- end
-
- it "generates excerpts from post content" do
- Dir.mktmpdir do |tmpdir|
- posts_dir = File.join(tmpdir, "posts", "2025", "11")
- FileUtils.mkdir_p(posts_dir)
-
- post_content = <<~MARKDOWN
- ---
- Title: Test Post
- Author: Greg Graffin
- Date: 5th November, 2025
- Timestamp: 2025-11-05T10:00:00-08:00
- ---
-
- This is a test post with some content. It should generate an excerpt.
-
- 
-
- More content with a [link](https://example.net).
- MARKDOWN
-
- File.write(File.join(posts_dir, "test.md"), post_content)
-
- posts_by_year = repo.read_posts(File.join(tmpdir, "posts"))
- post = posts_by_year.all_posts.first
-
- expect(post.excerpt).to include("test post")
- expect(post.excerpt).not_to include("![")
- expect(post.excerpt).to include("link")
- expect(post.excerpt).not_to include("[link]")
- end
- end
- end
-end
diff --git a/spec/posts/repo_test.rb b/spec/posts/repo_test.rb
new file mode 100644
index 0000000..f741c62
--- /dev/null
+++ b/spec/posts/repo_test.rb
@@ -0,0 +1,73 @@
+require "test_helper"
+require "fileutils"
+require "tmpdir"
+
+class Pressa::Posts::PostRepoTest < Minitest::Test
+ def repo
+ @repo ||= Pressa::Posts::PostRepo.new
+ end
+
+ def test_read_posts_reads_and_organizes_posts_by_year_and_month
+ Dir.mktmpdir do |tmpdir|
+ posts_dir = File.join(tmpdir, "posts", "2025", "11")
+ FileUtils.mkdir_p(posts_dir)
+
+ post_content = <<~MARKDOWN
+ ---
+ Title: Shredding in November
+ Author: Shaun White
+ Date: 5th November, 2025
+ Timestamp: 2025-11-05T10:00:00-08:00
+ ---
+
+ Had an epic day at Whistler. The powder was deep and the lines were short.
+ MARKDOWN
+
+ File.write(File.join(posts_dir, "shredding.md"), post_content)
+
+ posts_by_year = repo.read_posts(File.join(tmpdir, "posts"))
+
+ assert_equal(1, posts_by_year.all_posts.length)
+
+ post = posts_by_year.all_posts.first
+ assert_equal("Shredding in November", post.title)
+ assert_equal("Shaun White", post.author)
+ assert_equal("shredding", post.slug)
+ assert_equal(2025, post.year)
+ assert_equal(11, post.month)
+ assert_equal("/posts/2025/11/shredding", post.path)
+ end
+ end
+
+ def test_read_posts_generates_excerpts_from_post_content
+ Dir.mktmpdir do |tmpdir|
+ posts_dir = File.join(tmpdir, "posts", "2025", "11")
+ FileUtils.mkdir_p(posts_dir)
+
+ post_content = <<~MARKDOWN
+ ---
+ Title: Test Post
+ Author: Greg Graffin
+ Date: 5th November, 2025
+ Timestamp: 2025-11-05T10:00:00-08:00
+ ---
+
+ This is a test post with some content. It should generate an excerpt.
+
+ 
+
+ More content with a [link](https://example.net).
+ MARKDOWN
+
+ File.write(File.join(posts_dir, "test.md"), post_content)
+
+ posts_by_year = repo.read_posts(File.join(tmpdir, "posts"))
+ post = posts_by_year.all_posts.first
+
+ assert_includes(post.excerpt, "test post")
+ refute_includes(post.excerpt, "![")
+ assert_includes(post.excerpt, "link")
+ refute_includes(post.excerpt, "[link]")
+ end
+ end
+end
diff --git a/spec/site_generator_spec.rb b/spec/site_generator_test.rb
similarity index 55%
rename from spec/site_generator_spec.rb
rename to spec/site_generator_test.rb
index 9b04ab1..93b965b 100644
--- a/spec/site_generator_spec.rb
+++ b/spec/site_generator_test.rb
@@ -1,10 +1,10 @@
-require "spec_helper"
+require "test_helper"
require "fileutils"
require "tmpdir"
-RSpec.describe Pressa::SiteGenerator do
- let(:site) do
- Pressa::Site.new(
+class Pressa::SiteGeneratorTest < Minitest::Test
+ def site
+ @site ||= Pressa::Site.new(
author: "Sami Samhuri",
email: "sami@samhuri.net",
title: "samhuri.net",
@@ -15,23 +15,23 @@ RSpec.describe Pressa::SiteGenerator do
)
end
- it "rejects a target path that matches the source path" do
+ def test_rejects_a_target_path_that_matches_the_source_path
Dir.mktmpdir do |dir|
FileUtils.mkdir_p(File.join(dir, "public"))
source_file = File.join(dir, "public", "keep.txt")
File.write(source_file, "safe")
- generator = described_class.new(site:)
-
- expect {
+ generator = Pressa::SiteGenerator.new(site:)
+ error = assert_raises(ArgumentError) do
generator.generate(source_path: dir, target_path: dir)
- }.to raise_error(ArgumentError, /must not be the same as or contain source_path/)
+ end
- expect(File.read(source_file)).to eq("safe")
+ assert_match(/must not be the same as or contain source_path/, error.message)
+ assert_equal("safe", File.read(source_file))
end
end
- it "does not copy ignored dotfiles from public" do
+ def test_does_not_copy_ignored_dotfiles_from_public
Dir.mktmpdir do |dir|
source_path = File.join(dir, "source")
target_path = File.join(dir, "target")
@@ -42,11 +42,11 @@ RSpec.describe Pressa::SiteGenerator do
File.write(File.join(public_path, ".gitkeep"), "")
File.write(File.join(public_path, "visible.txt"), "ok")
- described_class.new(site:).generate(source_path:, target_path:)
+ Pressa::SiteGenerator.new(site:).generate(source_path:, target_path:)
- expect(File.exist?(File.join(target_path, "visible.txt"))).to be(true)
- expect(File.exist?(File.join(target_path, ".DS_Store"))).to be(false)
- expect(File.exist?(File.join(target_path, ".gitkeep"))).to be(false)
+ assert(File.exist?(File.join(target_path, "visible.txt")))
+ refute(File.exist?(File.join(target_path, ".DS_Store")))
+ refute(File.exist?(File.join(target_path, ".gitkeep")))
end
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
deleted file mode 100644
index ff5a9c9..0000000
--- a/spec/spec_helper.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require_relative "../lib/pressa"
-
-RSpec.configure do |config|
- config.expect_with :rspec do |expectations|
- expectations.include_chain_clauses_in_custom_matcher_descriptions = true
- end
-
- config.mock_with :rspec do |mocks|
- mocks.verify_partial_doubles = true
- end
-
- config.shared_context_metadata_behavior = :apply_to_host_groups
- config.filter_run_when_matching :focus
- config.example_status_persistence_file_path = "spec/examples.txt"
- config.disable_monkey_patching!
- config.warnings = true
-
- if config.files_to_run.one?
- config.default_formatter = "doc"
- end
-
- config.profile_examples = 10
- config.order = :random
- Kernel.srand config.seed
-end
diff --git a/spec/test_helper.rb b/spec/test_helper.rb
new file mode 100644
index 0000000..6c45c43
--- /dev/null
+++ b/spec/test_helper.rb
@@ -0,0 +1,2 @@
+require_relative "../lib/pressa"
+require "minitest/autorun"
diff --git a/spec/views/layout_spec.rb b/spec/views/layout_test.rb
similarity index 57%
rename from spec/views/layout_spec.rb
rename to spec/views/layout_test.rb
index 741722f..5eac0f5 100644
--- a/spec/views/layout_spec.rb
+++ b/spec/views/layout_test.rb
@@ -1,7 +1,7 @@
-require "spec_helper"
+require "test_helper"
-RSpec.describe Pressa::Views::Layout do
- let(:test_content_view) do
+class Pressa::Views::LayoutTest < Minitest::Test
+ def test_content_view
Class.new(Phlex::HTML) do
def view_template
article do
@@ -11,8 +11,8 @@ RSpec.describe Pressa::Views::Layout do
end.new
end
- let(:site) do
- Pressa::Site.new(
+ def site
+ @site ||= Pressa::Site.new(
author: "Sami Samhuri",
email: "sami@samhuri.net",
title: "samhuri.net",
@@ -21,31 +21,31 @@ RSpec.describe Pressa::Views::Layout do
)
end
- it "renders child components as HTML instead of escaped text" do
- html = described_class.new(
+ def test_rendering_child_components_as_html_instead_of_escaped_text
+ html = Pressa::Views::Layout.new(
site:,
canonical_url: "https://samhuri.net/posts/",
content: test_content_view
).call
- expect(html).to include("")
- expect(html).to include("Hello
")
- expect(html).not_to include("<article>")
+ assert_includes(html, "")
+ assert_includes(html, "Hello
")
+ refute_includes(html, "<article>")
end
- it "keeps escaping enabled for untrusted string fields" do
+ def test_keeps_escaping_enabled_for_untrusted_string_fields
subtitle = "
"
- html = described_class.new(
+ html = Pressa::Views::Layout.new(
site:,
canonical_url: "https://samhuri.net/posts/",
page_subtitle: subtitle,
content: test_content_view
).call
- expect(html).to include("samhuri.net: <img src=x onerror=alert(1)>")
+ assert_includes(html, "samhuri.net: <img src=x onerror=alert(1)>")
end
- it "preserves absolute stylesheet URLs" do
+ def test_preserves_absolute_stylesheet_urls
cdn_site = Pressa::Site.new(
author: "Sami Samhuri",
email: "sami@samhuri.net",
@@ -55,12 +55,12 @@ RSpec.describe Pressa::Views::Layout do
styles: [Pressa::Stylesheet.new(href: "https://cdn.example.com/site.css")]
)
- html = described_class.new(
+ html = Pressa::Views::Layout.new(
site: cdn_site,
canonical_url: "https://samhuri.net/posts/",
content: test_content_view
).call
- expect(html).to include(%())
+ assert_includes(html, %())
end
end