hyde_admin 0.0.13 → 0.0.14
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/.gitignore +3 -1
- data/.rspec +3 -0
- data/CHANGELOG.md +31 -0
- data/CLAUDE.md +56 -0
- data/Gemfile +8 -0
- data/bin/admin_views/admin_layout.html.erb +76 -51
- data/bin/admin_views/configuration.erb +4 -4
- data/bin/admin_views/dashboard.erb +37 -1
- data/bin/admin_views/editor_html.erb +21 -18
- data/bin/admin_views/editor_js.erb +112 -111
- data/bin/admin_views/files/edit.erb +4 -4
- data/bin/admin_views/files/listing.erb +14 -14
- data/bin/admin_views/posts/edit.erb +159 -62
- data/bin/admin_views/posts/listing.erb +10 -10
- data/bin/admin_views/search.erb +42 -0
- data/bin/admin_views/upload_image_form.erb +5 -5
- data/bin/hyde_admin.ru +165 -11
- data/bin/hyde_assets/hyde_admin.css +58 -0
- data/bin/hyde_assets/hyde_admin.js +121 -18
- data/bin/i18n/en.yml +20 -1
- data/bin/i18n/fr.yml +21 -2
- data/hyde_admin.gemspec +6 -8
- data/lib/hyde_admin/version.rb +1 -1
- data/spec/files/create_file_spec.rb +92 -0
- data/spec/mid_helpers_spec.rb +105 -0
- data/spec/posts/create_draft_spec.rb +77 -0
- data/spec/posts/create_page_spec.rb +75 -0
- data/spec/posts/create_post_spec.rb +100 -0
- data/spec/search/global_search_spec.rb +92 -0
- data/spec/spec_helper.rb +25 -0
- metadata +26 -34
- data/.idea/.gitignore +0 -8
- data/.idea/hyde_admin.iml +0 -25
- data/.idea/misc.xml +0 -4
- data/.idea/modules.xml +0 -8
- data/.idea/vcs.xml +0 -6
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require 'rack/test'
|
|
2
|
+
|
|
3
|
+
RSpec.describe "Create files and directories", type: :system do
|
|
4
|
+
include Rack::Test::Methods
|
|
5
|
+
|
|
6
|
+
def app
|
|
7
|
+
Mid
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
let(:tmpdir) { Dir.mktmpdir }
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
FileUtils.cp(File.join(File.dirname(__FILE__), '../../bin/hyde_admin.yml'), File.join(tmpdir, 'hyde_admin.yml'))
|
|
14
|
+
allow(Dir).to receive(:pwd).and_return(tmpdir)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
after do
|
|
18
|
+
FileUtils.remove_entry(tmpdir)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe "directory creation" do
|
|
22
|
+
it "creates a new directory" do
|
|
23
|
+
post "/files/create_dir?dir_path=#{tmpdir}", {
|
|
24
|
+
directory_name: "my_new_folder"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
expect(last_response).to be_redirect
|
|
28
|
+
|
|
29
|
+
new_dir = File.join(tmpdir, 'my_new_folder')
|
|
30
|
+
expect(File.directory?(new_dir)).to be true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "creates a nested directory inside an existing one" do
|
|
34
|
+
parent = File.join(tmpdir, 'parent')
|
|
35
|
+
FileUtils.mkdir_p(parent)
|
|
36
|
+
|
|
37
|
+
post "/files/create_dir?dir_path=#{parent}", {
|
|
38
|
+
directory_name: "child"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
expect(File.directory?(File.join(parent, 'child'))).to be true
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe "file creation" do
|
|
46
|
+
it "creates an empty file" do
|
|
47
|
+
post "/files/create_file?dir_path=#{tmpdir}", {
|
|
48
|
+
file_name: "new_page.html"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
expect(last_response).to be_redirect
|
|
52
|
+
|
|
53
|
+
new_file = File.join(tmpdir, 'new_page.html')
|
|
54
|
+
expect(File.exist?(new_file)).to be true
|
|
55
|
+
expect(File.read(new_file)).to eq("")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "creates a file inside a subdirectory" do
|
|
59
|
+
subdir = File.join(tmpdir, 'assets')
|
|
60
|
+
FileUtils.mkdir_p(subdir)
|
|
61
|
+
|
|
62
|
+
post "/files/create_file?dir_path=#{subdir}", {
|
|
63
|
+
file_name: "style.css"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
new_file = File.join(subdir, 'style.css')
|
|
67
|
+
expect(File.exist?(new_file)).to be true
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe "file upload" do
|
|
72
|
+
it "uploads a file" do
|
|
73
|
+
tempfile = Tempfile.new(['upload', '.txt'])
|
|
74
|
+
tempfile.write("file content here")
|
|
75
|
+
tempfile.rewind
|
|
76
|
+
|
|
77
|
+
uploaded_file = Rack::Test::UploadedFile.new(tempfile.path, "text/plain", false)
|
|
78
|
+
|
|
79
|
+
post "/files/create?dir_path=#{tmpdir}", {
|
|
80
|
+
files: uploaded_file
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
expect(last_response).to be_redirect
|
|
84
|
+
|
|
85
|
+
uploaded = File.join(tmpdir, File.basename(tempfile.path))
|
|
86
|
+
expect(File.exist?(uploaded)).to be true
|
|
87
|
+
expect(File.read(uploaded)).to eq("file content here")
|
|
88
|
+
|
|
89
|
+
tempfile.close!
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
RSpec.describe Mid do
|
|
2
|
+
describe ".transliterate_title_for_url" do
|
|
3
|
+
it "converts a simple title to a URL-friendly slug" do
|
|
4
|
+
expect(Mid.transliterate_title_for_url("Mon article")).to eq("mon-article")
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
it "removes trailing hyphens" do
|
|
8
|
+
expect(Mid.transliterate_title_for_url("Mon fichier !")).to eq("mon-fichier")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "removes leading and trailing spaces" do
|
|
12
|
+
expect(Mid.transliterate_title_for_url(" Hello World ")).to eq("hello-world")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "collapses multiple spaces into a single hyphen" do
|
|
16
|
+
expect(Mid.transliterate_title_for_url("Hello World")).to eq("hello-world")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "handles accented characters" do
|
|
20
|
+
expect(Mid.transliterate_title_for_url("Écrire un résumé")).to eq("ecrire-un-resume")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "preserves numbers" do
|
|
24
|
+
expect(Mid.transliterate_title_for_url("Article 2026")).to eq("article-2026")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "handles titles with only special characters" do
|
|
28
|
+
expect(Mid.transliterate_title_for_url("!!!")).to eq("")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "handles titles ending with special characters" do
|
|
32
|
+
expect(Mid.transliterate_title_for_url("Mon titre...")).to eq("mon-titre")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "removes punctuation" do
|
|
36
|
+
expect(Mid.transliterate_title_for_url("Hello, World!")).to eq("hello-world")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe ".urlize" do
|
|
41
|
+
it "generates a filename with date for posts" do
|
|
42
|
+
expect(Mid.urlize("2026-03-17", "Mon article", true)).to eq("2026-03-17-mon-article")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "generates a filename without date for pages" do
|
|
46
|
+
expect(Mid.urlize("2026-03-17", "Ma page", false)).to eq("ma-page")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "does not produce trailing hyphens" do
|
|
50
|
+
result = Mid.urlize("2026-03-17", "Mon fichier !", true)
|
|
51
|
+
expect(result).to eq("2026-03-17-mon-fichier")
|
|
52
|
+
expect(result).not_to end_with("-")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe ".extract_header_str" do
|
|
57
|
+
it "extracts YAML front matter" do
|
|
58
|
+
content = "---\ntitle: Hello\nlayout: post\n---\nBody content"
|
|
59
|
+
expect(Mid.extract_header_str(content)).to eq("\ntitle: Hello\nlayout: post\n")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "returns nil when no front matter" do
|
|
63
|
+
expect(Mid.extract_header_str("No front matter here")).to be_nil
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe ".extract_header" do
|
|
68
|
+
it "parses front matter into a hash" do
|
|
69
|
+
content = "---\ntitle: Hello\nlayout: post\n---\nBody"
|
|
70
|
+
result = Mid.extract_header(content)
|
|
71
|
+
expect(result).to eq({ "title" => "Hello", "layout" => "post" })
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "returns empty hash for content without front matter" do
|
|
75
|
+
expect(Mid.extract_header("No front matter")).to eq({})
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
describe ".remove_header" do
|
|
80
|
+
it "removes YAML front matter from content" do
|
|
81
|
+
content = "---\ntitle: Hello\n---\nBody content"
|
|
82
|
+
expect(Mid.remove_header(content).strip).to eq("Body content")
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe ".extract_tags" do
|
|
87
|
+
it "parses comma-separated tags" do
|
|
88
|
+
expect(Mid.extract_tags("ruby,jekyll,web")).to eq(["ruby", "jekyll", "web"])
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "handles bracketed tags" do
|
|
92
|
+
expect(Mid.extract_tags("[ruby,jekyll]")).to eq(["ruby", "jekyll"])
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
describe ".safe_path?" do
|
|
97
|
+
it "accepts paths within the working directory" do
|
|
98
|
+
expect(Mid.safe_path?(File.join(Dir.pwd, "some_file.md"))).to be true
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "rejects paths outside the working directory" do
|
|
102
|
+
expect(Mid.safe_path?("/etc/passwd")).to be false
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require 'rack/test'
|
|
2
|
+
|
|
3
|
+
RSpec.describe "Create a draft", type: :system do
|
|
4
|
+
include Rack::Test::Methods
|
|
5
|
+
|
|
6
|
+
def app
|
|
7
|
+
Mid
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
let(:tmpdir) { Dir.mktmpdir }
|
|
11
|
+
let(:drafts_dir) { File.join(tmpdir, '_drafts') }
|
|
12
|
+
let(:posts_dir) { File.join(tmpdir, '_posts') }
|
|
13
|
+
let(:layouts_dir) { File.join(tmpdir, '_layouts') }
|
|
14
|
+
|
|
15
|
+
before do
|
|
16
|
+
FileUtils.mkdir_p(drafts_dir)
|
|
17
|
+
FileUtils.mkdir_p(posts_dir)
|
|
18
|
+
FileUtils.mkdir_p(layouts_dir)
|
|
19
|
+
File.write(File.join(layouts_dir, 'post.html'), '<html>{{ content }}</html>')
|
|
20
|
+
FileUtils.cp(File.join(File.dirname(__FILE__), '../../bin/hyde_admin.yml'), File.join(tmpdir, 'hyde_admin.yml'))
|
|
21
|
+
|
|
22
|
+
allow(Dir).to receive(:pwd).and_return(tmpdir)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
after do
|
|
26
|
+
FileUtils.remove_entry(tmpdir)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "creates a draft file in _drafts with date prefix" do
|
|
30
|
+
post "/drafts", {
|
|
31
|
+
file: "",
|
|
32
|
+
title: "Work in progress",
|
|
33
|
+
date: "2026-04-10 14:00:00 +0200",
|
|
34
|
+
tags: "draft",
|
|
35
|
+
layout: "post",
|
|
36
|
+
format: "md",
|
|
37
|
+
content: "This is still a draft.",
|
|
38
|
+
new_file: ""
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
expect(last_response).to be_redirect
|
|
42
|
+
|
|
43
|
+
created_files = Dir.glob(File.join(drafts_dir, '*.md'))
|
|
44
|
+
expect(created_files.size).to eq(1)
|
|
45
|
+
|
|
46
|
+
filename = File.basename(created_files.first)
|
|
47
|
+
expect(filename).to eq("2026-04-10-work-in-progress.md")
|
|
48
|
+
|
|
49
|
+
content = File.read(created_files.first)
|
|
50
|
+
headers = Mid.extract_header(content)
|
|
51
|
+
|
|
52
|
+
expect(headers['layout']).to eq("post")
|
|
53
|
+
expect(headers['title']).to eq("Work in progress")
|
|
54
|
+
|
|
55
|
+
body = Mid.remove_header(content)
|
|
56
|
+
expect(body).to include("This is still a draft.")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "publishes a draft by moving it to _posts" do
|
|
60
|
+
post "/drafts", {
|
|
61
|
+
file: "",
|
|
62
|
+
title: "Ready to publish",
|
|
63
|
+
date: "2026-04-10 14:00:00 +0200",
|
|
64
|
+
tags: "news",
|
|
65
|
+
layout: "post",
|
|
66
|
+
format: "md",
|
|
67
|
+
content: "This draft is ready.",
|
|
68
|
+
publish: "publish",
|
|
69
|
+
new_file: ""
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
expect(Dir.glob(File.join(drafts_dir, '*'))).to be_empty
|
|
73
|
+
published_files = Dir.glob(File.join(posts_dir, '*.md'))
|
|
74
|
+
expect(published_files.size).to eq(1)
|
|
75
|
+
expect(File.basename(published_files.first)).to eq("2026-04-10-ready-to-publish.md")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require 'rack/test'
|
|
2
|
+
|
|
3
|
+
RSpec.describe "Create a page", type: :system do
|
|
4
|
+
include Rack::Test::Methods
|
|
5
|
+
|
|
6
|
+
def app
|
|
7
|
+
Mid
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
let(:tmpdir) { Dir.mktmpdir }
|
|
11
|
+
let(:pages_dir) { File.join(tmpdir, '_pages') }
|
|
12
|
+
let(:layouts_dir) { File.join(tmpdir, '_layouts') }
|
|
13
|
+
|
|
14
|
+
before do
|
|
15
|
+
FileUtils.mkdir_p(pages_dir)
|
|
16
|
+
FileUtils.mkdir_p(layouts_dir)
|
|
17
|
+
File.write(File.join(layouts_dir, 'default.html'), '<html>{{ content }}</html>')
|
|
18
|
+
FileUtils.cp(File.join(File.dirname(__FILE__), '../../bin/hyde_admin.yml'), File.join(tmpdir, 'hyde_admin.yml'))
|
|
19
|
+
|
|
20
|
+
allow(Dir).to receive(:pwd).and_return(tmpdir)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
after do
|
|
24
|
+
FileUtils.remove_entry(tmpdir)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "creates a page file without date prefix in filename" do
|
|
28
|
+
post "/pages", {
|
|
29
|
+
file: "",
|
|
30
|
+
title: "About me",
|
|
31
|
+
date: "2026-03-17 10:00:00 +0100",
|
|
32
|
+
tags: "info",
|
|
33
|
+
layout: "default",
|
|
34
|
+
format: "html",
|
|
35
|
+
content: "This is the about page.",
|
|
36
|
+
new_file: ""
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
expect(last_response).to be_redirect
|
|
40
|
+
|
|
41
|
+
created_files = Dir.glob(File.join(pages_dir, '*'))
|
|
42
|
+
expect(created_files.size).to eq(1)
|
|
43
|
+
|
|
44
|
+
filename = File.basename(created_files.first)
|
|
45
|
+
expect(filename).to eq("about-me.html")
|
|
46
|
+
expect(filename).not_to match(/^\d{4}-\d{2}-\d{2}/)
|
|
47
|
+
|
|
48
|
+
content = File.read(created_files.first)
|
|
49
|
+
headers = Mid.extract_header(content)
|
|
50
|
+
|
|
51
|
+
expect(headers['layout']).to eq("default")
|
|
52
|
+
expect(headers['title']).to eq("About me")
|
|
53
|
+
expect(headers['date']).to eq("2026-03-17 10:00:00 +0100")
|
|
54
|
+
|
|
55
|
+
body = Mid.remove_header(content)
|
|
56
|
+
expect(body).to include("This is the about page.")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "creates a page in markdown format" do
|
|
60
|
+
post "/pages", {
|
|
61
|
+
file: "",
|
|
62
|
+
title: "Contact",
|
|
63
|
+
date: "2026-05-01 09:00:00 +0200",
|
|
64
|
+
tags: "",
|
|
65
|
+
layout: "default",
|
|
66
|
+
format: "md",
|
|
67
|
+
content: "# Contact\n\nSend me an email.",
|
|
68
|
+
new_file: ""
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
created_files = Dir.glob(File.join(pages_dir, '*.md'))
|
|
72
|
+
expect(created_files.size).to eq(1)
|
|
73
|
+
expect(File.basename(created_files.first)).to eq("contact.md")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
require 'rack/test'
|
|
2
|
+
|
|
3
|
+
RSpec.describe "Create a post", type: :system do
|
|
4
|
+
include Rack::Test::Methods
|
|
5
|
+
|
|
6
|
+
def app
|
|
7
|
+
Mid
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
let(:tmpdir) { Dir.mktmpdir }
|
|
11
|
+
let(:posts_dir) { File.join(tmpdir, '_posts') }
|
|
12
|
+
let(:layouts_dir) { File.join(tmpdir, '_layouts') }
|
|
13
|
+
|
|
14
|
+
before do
|
|
15
|
+
FileUtils.mkdir_p(posts_dir)
|
|
16
|
+
FileUtils.mkdir_p(layouts_dir)
|
|
17
|
+
File.write(File.join(layouts_dir, 'post.html'), '<html>{{ content }}</html>')
|
|
18
|
+
File.write(File.join(layouts_dir, 'default.html'), '<html>{{ content }}</html>')
|
|
19
|
+
FileUtils.cp(File.join(File.dirname(__FILE__), '../../bin/hyde_admin.yml'), File.join(tmpdir, 'hyde_admin.yml'))
|
|
20
|
+
|
|
21
|
+
allow(Dir).to receive(:pwd).and_return(tmpdir)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
after do
|
|
25
|
+
FileUtils.remove_entry(tmpdir)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "creates a post file with correct date, format, layout and content" do
|
|
29
|
+
post "/posts", {
|
|
30
|
+
file: "",
|
|
31
|
+
title: "Mon premier article",
|
|
32
|
+
date: "2026-03-17 10:30:00 +0100",
|
|
33
|
+
tags: "ruby,jekyll",
|
|
34
|
+
layout: "post",
|
|
35
|
+
format: "md",
|
|
36
|
+
content: "Ceci est le contenu de mon article.",
|
|
37
|
+
new_file: ""
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
expect(last_response).to be_redirect
|
|
41
|
+
|
|
42
|
+
created_files = Dir.glob(File.join(posts_dir, '*.md'))
|
|
43
|
+
expect(created_files.size).to eq(1)
|
|
44
|
+
|
|
45
|
+
filename = File.basename(created_files.first)
|
|
46
|
+
expect(filename).to eq("2026-03-17-mon-premier-article.md")
|
|
47
|
+
|
|
48
|
+
content = File.read(created_files.first)
|
|
49
|
+
|
|
50
|
+
expect(content).to start_with("---\n")
|
|
51
|
+
expect(content).to match(/^---\n.*^---$/m)
|
|
52
|
+
|
|
53
|
+
headers = Mid.extract_header(content)
|
|
54
|
+
|
|
55
|
+
expect(headers['date']).to eq("2026-03-17 10:30:00 +0100")
|
|
56
|
+
expect(headers['layout']).to eq("post")
|
|
57
|
+
expect(headers['title']).to eq("Mon premier article")
|
|
58
|
+
expect(headers['tags']).to eq("ruby,jekyll")
|
|
59
|
+
|
|
60
|
+
body = Mid.remove_header(content)
|
|
61
|
+
expect(body).to include("Ceci est le contenu de mon article.")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "creates a post in html format" do
|
|
65
|
+
post "/posts", {
|
|
66
|
+
file: "",
|
|
67
|
+
title: "Article HTML",
|
|
68
|
+
date: "2026-01-15 08:00:00 +0100",
|
|
69
|
+
tags: "web",
|
|
70
|
+
layout: "default",
|
|
71
|
+
format: "html",
|
|
72
|
+
content: "<p>Some HTML content</p>",
|
|
73
|
+
new_file: ""
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
created_files = Dir.glob(File.join(posts_dir, '*.html'))
|
|
77
|
+
expect(created_files.size).to eq(1)
|
|
78
|
+
expect(File.basename(created_files.first)).to eq("2026-01-15-article-html.html")
|
|
79
|
+
|
|
80
|
+
headers = Mid.extract_header(File.read(created_files.first))
|
|
81
|
+
expect(headers['layout']).to eq("default")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "creates a post with accented title" do
|
|
85
|
+
post "/posts", {
|
|
86
|
+
file: "",
|
|
87
|
+
title: "Écrire un résumé",
|
|
88
|
+
date: "2026-06-01 12:00:00 +0200",
|
|
89
|
+
tags: "french",
|
|
90
|
+
layout: "post",
|
|
91
|
+
format: "md",
|
|
92
|
+
content: "Some accented content.",
|
|
93
|
+
new_file: ""
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
created_files = Dir.glob(File.join(posts_dir, '*.md'))
|
|
97
|
+
expect(created_files.size).to eq(1)
|
|
98
|
+
expect(File.basename(created_files.first)).to eq("2026-06-01-ecrire-un-resume.md")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require 'rack/test'
|
|
2
|
+
|
|
3
|
+
RSpec.describe "Global search", type: :system do
|
|
4
|
+
include Rack::Test::Methods
|
|
5
|
+
|
|
6
|
+
def app
|
|
7
|
+
Mid
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
let(:tmpdir) { Dir.mktmpdir }
|
|
11
|
+
let(:posts_dir) { File.join(tmpdir, '_posts') }
|
|
12
|
+
let(:pages_dir) { File.join(tmpdir, '_pages') }
|
|
13
|
+
let(:drafts_dir) { File.join(tmpdir, '_drafts') }
|
|
14
|
+
let(:layouts_dir) { File.join(tmpdir, '_layouts') }
|
|
15
|
+
|
|
16
|
+
before do
|
|
17
|
+
FileUtils.mkdir_p(posts_dir)
|
|
18
|
+
FileUtils.mkdir_p(pages_dir)
|
|
19
|
+
FileUtils.mkdir_p(drafts_dir)
|
|
20
|
+
FileUtils.mkdir_p(layouts_dir)
|
|
21
|
+
File.write(File.join(layouts_dir, 'default.html'), '<html>{{ content }}</html>')
|
|
22
|
+
FileUtils.cp(File.join(File.dirname(__FILE__), '../../bin/hyde_admin.yml'), File.join(tmpdir, 'hyde_admin.yml'))
|
|
23
|
+
|
|
24
|
+
File.write(File.join(posts_dir, '2026-03-17-hello-world.md'),
|
|
25
|
+
"---\ntitle: Hello World\ntags: ruby,test\nlayout: default\n---\nThis is the body of hello world.")
|
|
26
|
+
File.write(File.join(posts_dir, '2026-03-16-another-post.md'),
|
|
27
|
+
"---\ntitle: Another Post\ntags: jekyll\nlayout: default\n---\nSomething else entirely.")
|
|
28
|
+
File.write(File.join(pages_dir, 'about.html'),
|
|
29
|
+
"---\ntitle: About Me\ntags: info\nlayout: default\n---\n<p>About page content</p>")
|
|
30
|
+
File.write(File.join(drafts_dir, '2026-03-17-draft-idea.md'),
|
|
31
|
+
"---\ntitle: Draft Idea\ntags: draft\nlayout: default\n---\nWork in progress content.")
|
|
32
|
+
|
|
33
|
+
allow(Dir).to receive(:pwd).and_return(tmpdir)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
after do
|
|
37
|
+
FileUtils.remove_entry(tmpdir)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "returns matching posts by title" do
|
|
41
|
+
get "/search", q: "Hello"
|
|
42
|
+
|
|
43
|
+
expect(last_response).to be_ok
|
|
44
|
+
expect(last_response.body).to include("Hello World")
|
|
45
|
+
expect(last_response.body).not_to include("Another Post")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "returns matching posts by tag" do
|
|
49
|
+
get "/search", q: "jekyll"
|
|
50
|
+
|
|
51
|
+
expect(last_response).to be_ok
|
|
52
|
+
expect(last_response.body).to include("Another Post")
|
|
53
|
+
expect(last_response.body).not_to include("Hello World")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "returns matching posts by body content" do
|
|
57
|
+
get "/search", q: "entirely"
|
|
58
|
+
|
|
59
|
+
expect(last_response).to be_ok
|
|
60
|
+
expect(last_response.body).to include("Another Post")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "searches across posts, pages and drafts" do
|
|
64
|
+
get "/search", q: "content"
|
|
65
|
+
|
|
66
|
+
expect(last_response).to be_ok
|
|
67
|
+
expect(last_response.body).to include("About Me")
|
|
68
|
+
expect(last_response.body).to include("Draft Idea")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "searches case-insensitively" do
|
|
72
|
+
get "/search", q: "hello"
|
|
73
|
+
|
|
74
|
+
expect(last_response).to be_ok
|
|
75
|
+
expect(last_response.body).to include("Hello World")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "returns no results for unknown query" do
|
|
79
|
+
get "/search", q: "zzzznotfound"
|
|
80
|
+
|
|
81
|
+
expect(last_response).to be_ok
|
|
82
|
+
expect(last_response.body).to include("0")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "ignores queries shorter than 2 characters" do
|
|
86
|
+
get "/search", q: "a"
|
|
87
|
+
|
|
88
|
+
expect(last_response).to be_ok
|
|
89
|
+
expect(last_response.body).not_to include("Hello World")
|
|
90
|
+
expect(last_response.body).not_to include("About Me")
|
|
91
|
+
end
|
|
92
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require 'i18n'
|
|
4
|
+
require 'date'
|
|
5
|
+
require 'cgi'
|
|
6
|
+
require 'roda'
|
|
7
|
+
require_relative '../lib/hyde_admin/version'
|
|
8
|
+
|
|
9
|
+
# Load hyde_admin.ru but skip the `run` call at the end
|
|
10
|
+
app_code = File.read(File.expand_path('../../bin/hyde_admin.ru', __FILE__))
|
|
11
|
+
# Remove the final `run ...` line that starts the Rack server
|
|
12
|
+
app_code = app_code.sub(/^run\s+.*\z/m, '')
|
|
13
|
+
eval(app_code, binding, File.expand_path('../../bin/hyde_admin.ru', __FILE__), 1)
|
|
14
|
+
|
|
15
|
+
RSpec.configure do |config|
|
|
16
|
+
config.expect_with :rspec do |expectations|
|
|
17
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
config.mock_with :rspec do |mocks|
|
|
21
|
+
mocks.verify_partial_doubles = true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
|
25
|
+
end
|