[Almirah.Spec] : cross_document_links_spec.rb ¶
1.1 References ¶
| # | | UL | DL | COV | DR |
| SC-AABI |
... |
|
|
|
|
| SC-AABJ |
A Markdown spec-to-spec link resolves to the target's generated page. |
SRS-088 |
|
|
|
| SC-AABK |
A Markdown spec-to-protocol link resolves to the target's generated page. |
SRS-088 |
|
|
|
| SC-AABL |
A Markdown decision-to-spec link resolves to the target's generated page. |
SRS-088 |
|
|
|
| SC-AABM |
A decision-to-decision Markdown link maps the source filename to the id-named page. |
SRS-088 |
|
|
|
| SC-AABN |
The source Markdown link stays a valid relative path on disk. |
SRS-089 |
|
|
|
| SC-AABO |
A double-bracket wiki link resolves by unique id, independent of folder. |
SRS-090 |
|
|
|
| SC-AABP |
A double-bracket link with an alias renders the alias as its visible text. |
SRS-091 |
|
|
|
| SC-AABQ |
An anchor in a double-bracket wiki link links to the fragment. |
SRS-092 |
|
|
|
| SC-AABR |
An anchor in a Markdown link path links to the fragment. |
SRS-092 |
|
|
|
| SC-AABS |
The relative URL is computed from the linking page, with forward slashes. |
SRS-093 |
|
|
|
| SC-AABT |
An unresolved cross-document link is rendered broken and reported. |
SRS-094 |
|
|
|
| SC-AABU |
External links (http, mailto) are left unchanged. |
SRS-095 |
|
|
|
1.2 Source Code ¶
# frozen_string_literal: true
require_relative 'spec_helper'
# Covers native cross-document links (ADR-186): Markdown relative links and
# [[wiki]] links across specifications, protocols, and decision records, plus
# anchors, broken-link reporting, and external-link passthrough. Each example is
# linked to the requirement it verifies via a "<REQ> ... >[SRS-NNN] </REQ>" comment.
RSpec.describe 'cross-document links', type: :aruba do
before do
write_file('myproject/project.yml', "specifications:\n input: []\n")
write_file('myproject/specifications/aaa/aaa.md', <<~MD)
# AAA Specification
[AAA-001] A requirement in AAA.
Spec link: [see BBB](../bbb/bbb.md).
Protocol link: [the protocol](../../tests/protocols/tp-700/tp-700.md).
Broken file: [missing](./nonexistent.md).
Broken wiki: [[does-not-exist]].
External: [example](https://example.com).
Mail: [mail](mailto:dev@example.com).
MD
write_file('myproject/specifications/bbb/bbb.md', <<~MD)
# BBB Specification
[BBB-001] A requirement in BBB.
Deep link: [AAA one](../aaa/aaa.md#AAA-001).
MD
write_file('myproject/tests/protocols/tp-700/tp-700.md', <<~MD)
# Test Protocol 700
Back to [AAA](../../../specifications/aaa/aaa.md).
MD
write_file('myproject/decisions/relx/adr-700-first.md', <<~MD)
---
title: "ADR-700: First"
---
# Status
| | Date | Status |
|:---:|---|---|
| * | 01-01-2025 | Accepted |
Decision link: [second](../rely/adr-701-second.md).
Spec link: [AAA req](../../specifications/aaa/aaa.md).
Wiki: [[adr-701]].
Wiki alias: [[adr-701|the second decision]].
Wiki anchor: [[aaa#AAA-001]].
MD
write_file('myproject/decisions/rely/adr-701-second.md', <<~MD)
---
title: "ADR-701: Second"
---
# Status
| | Date | Status |
|:---:|---|---|
| * | 01-01-2025 | Accepted |
Back: [[adr-700]].
MD
run_command_and_stop('almirah please myproject', fail_on_error: false)
end
def page(rel)
Nokogiri::HTML(File.read(expand_path("myproject/build/#{rel}")))
end
def link_by_text(doc, text)
doc.css('a, span').find { |n| n.text.strip == text }
end
# <REQ> A Markdown spec-to-spec link resolves to the target's generated page. >[SRS-088] </REQ>
it 'resolves a spec-to-spec Markdown link' do
link = link_by_text(page('specifications/aaa/aaa.html'), 'see BBB')
expect(link['href']).to eq('../bbb/bbb.html')
expect(link['class']).to include('external')
end
# <REQ> A Markdown spec-to-protocol link resolves to the target's generated page. >[SRS-088] </REQ>
it 'resolves a spec-to-protocol Markdown link' do
link = link_by_text(page('specifications/aaa/aaa.html'), 'the protocol')
expect(link['href']).to eq('../../tests/protocols/tp-700/tp-700.html')
end
# <REQ> A Markdown decision-to-spec link resolves to the target's generated page. >[SRS-088] </REQ>
it 'resolves a decision-to-spec Markdown link' do
link = link_by_text(page('decisions/relx/adr-700.html'), 'AAA req')
expect(link['href']).to eq('../../specifications/aaa/aaa.html')
end
# <REQ> A decision-to-decision Markdown link maps the source filename to the id-named page. >[SRS-088] </REQ>
it 'resolves a decision-to-decision Markdown link across folders to the id-named page' do
link = link_by_text(page('decisions/relx/adr-700.html'), 'second')
expect(link['href']).to eq('../rely/adr-701.html')
end
# <REQ> The source Markdown link stays a valid relative path on disk. >[SRS-089] </REQ>
it 'keeps the source Markdown link navigable on disk' do
src_dir = File.dirname(expand_path('myproject/specifications/aaa/aaa.md'))
expect(File.exist?(File.expand_path('../bbb/bbb.md', src_dir))).to be true
end
# <REQ> A double-bracket wiki link resolves by unique id, independent of folder. >[SRS-090] </REQ>
it 'resolves a [[wiki]] link by id regardless of folder' do
link = link_by_text(page('decisions/relx/adr-700.html'), 'adr-701')
expect(link['href']).to eq('../rely/adr-701.html')
end
# <REQ> A double-bracket link with an alias renders the alias as its visible text. >[SRS-091] </REQ>
it 'renders the alias of a [[target|alias]] link' do
link = link_by_text(page('decisions/relx/adr-700.html'), 'the second decision')
expect(link).not_to be_nil
expect(link['href']).to eq('../rely/adr-701.html')
end
# <REQ> An anchor in a double-bracket wiki link links to the fragment. >[SRS-092] </REQ>
it 'resolves a [[target#anchor]] wiki link to the fragment' do
link = link_by_text(page('decisions/relx/adr-700.html'), 'aaa#AAA-001')
expect(link['href']).to eq('../../specifications/aaa/aaa.html#AAA-001')
end
# <REQ> An anchor in a Markdown link path links to the fragment. >[SRS-092] </REQ>
it 'resolves a Markdown path#anchor link to the fragment' do
link = link_by_text(page('specifications/bbb/bbb.html'), 'AAA one')
expect(link['href']).to eq('../aaa/aaa.html#AAA-001')
end
# <REQ> The relative URL is computed from the linking page, with forward slashes. >[SRS-093] </REQ>
it 'computes a forward-slash relative URL from the linking page' do
link = link_by_text(page('decisions/relx/adr-700.html'), 'AAA req')
expect(link['href']).to eq('../../specifications/aaa/aaa.html')
expect(link['href']).not_to include('\\')
end
# <REQ> An unresolved cross-document link is rendered broken and reported. >[SRS-094] </REQ>
it 'renders unresolved links as broken and reports them' do
doc = page('specifications/aaa/aaa.html')
broken_texts = doc.css('.broken_link').map { |n| n.text.strip }
expect(broken_texts).to include('missing') # broken Markdown link
expect(broken_texts).to include('does-not-exist') # broken wiki link
expect(last_command_started.output).to match(/broken links/)
expect(last_command_started.output).to include('aaa')
end
# <REQ> External links (http, mailto) are left unchanged. >[SRS-095] </REQ>
it 'leaves http and mailto links unchanged' do
doc = page('specifications/aaa/aaa.html')
ext = link_by_text(doc, 'example')
expect(ext['href']).to eq('https://example.com')
expect(ext['class'].to_s).not_to include('broken_link')
mail = link_by_text(doc, 'mail')
expect(mail['href']).to eq('mailto:dev@example.com')
end
end