Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ jobs:
run: npm ci
- name: Run stylelint
run: npm run lint:css
- name: Run JS tests
run: npm run test:js
11 changes: 11 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ bundle exec rake verify_generated
- **Test Location:** `test/` directory
- **Test Helper:** `test/lib/helper.rb`

### JavaScript Tests

The template JavaScript is tested with Node's built-in test runner. It needs Node 20 or later and no npm packages.

```bash
npm run test:js
```

- **Test Location:** `test/js/` directory
- **Test Helper:** `test/js/helper.js`

## Linting

### RuboCop (Ruby Code)
Expand Down
4 changes: 0 additions & 4 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,3 @@ if ENV['PRISM_VERSION'] == 'head'
elsif ENV['PRISM_VERSION']
gem 'prism', ENV['PRISM_VERSION']
end

platforms :ruby do
gem 'mini_racer' # For testing the searcher.js file
end
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"name": "rdoc",
"private": true,
"scripts": {
"lint:css": "stylelint 'lib/rdoc/generator/template/**/*.css'"
"lint:css": "stylelint 'lib/rdoc/generator/template/**/*.css'",
"test:js": "node --test test/js/*/*.test.js"
},
"devDependencies": {
"stylelint": "^16.25.0",
Expand Down
162 changes: 162 additions & 0 deletions test/js/aliki/bash_highlighter.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
'use strict';

const { test } = require('node:test');
const { assert, assertIncludes, refuteIncludes, loadHighlighter } = require('../helper');

const highlight = loadHighlighter('aliki/js/bash_highlighter.js');

test('prompts', () => {
// $ followed by space or end of line is a prompt
for (const [input, expected] of [
['$ bundle exec rake', '<span class="sh-prompt">$</span>'],
[' $ npm install', '<span class="sh-prompt">$</span>'],
['$', '<span class="sh-prompt">$</span>'],
]) {
assertIncludes(highlight(input), expected, `Failed for: ${input}`);
}

// $VAR is a variable, not a prompt
refuteIncludes(highlight('$HOME/bin'), '<span class="sh-prompt">');
});

test('comments', () => {
for (const [input, expected] of [
['# This is a comment', '<span class="sh-comment"># This is a comment</span>'],
['bundle exec rake # Run tests', '<span class="sh-comment"># Run tests</span>'],
]) {
assertIncludes(highlight(input), expected, `Failed for: ${input}`);
}
});

test('options', () => {
for (const [input, expected] of [
['ls -l', '<span class="sh-option">-l</span>'],
['npm install --save-dev', '<span class="sh-option">--save-dev</span>'],
['git commit --message=fix', '<span class="sh-option">--message=fix</span>'],
['ls -la --color=auto', ['<span class="sh-option">-la</span>', '<span class="sh-option">--color=auto</span>']],
]) {
for (const exp of [expected].flat()) {
assertIncludes(highlight(input), exp, `Failed for: ${input}`);
}
}
});

test('options with quoted values', () => {
// Option with quoted value should stop at the quote
let result = highlight('git commit --message="initial commit"');
assertIncludes(result, '<span class="sh-option">--message=</span>');
assertIncludes(result, '<span class="sh-string">&quot;initial commit&quot;</span>');

// Single-quoted value
result = highlight("git commit --message='initial commit'");
assertIncludes(result, '<span class="sh-option">--message=</span>');
assertIncludes(result, '<span class="sh-string">&#39;initial commit&#39;</span>');
});

test('strings', () => {
for (const [input, expected] of [
['echo "hello world"', '<span class="sh-string">&quot;hello world&quot;</span>'],
["echo 'hello world'", '<span class="sh-string">&#39;hello world&#39;</span>'],
['echo "hello \\"world\\""', '<span class="sh-string">&quot;hello \\&quot;world\\&quot;&quot;</span>'],
['npx @herb-tools/linter "**/*.rhtml"', '<span class="sh-string">&quot;**/*.rhtml&quot;</span>'],
]) {
assertIncludes(highlight(input), expected, `Failed for: ${input}`);
}
});

test('commands', () => {
const result = highlight('bundle exec rake');
assertIncludes(result, '<span class="sh-command">bundle</span>');
// Only the first word is highlighted as command
refuteIncludes(result, '<span class="sh-command">exec</span>');
refuteIncludes(result, '<span class="sh-command">rake</span>');
});

test('path commands', () => {
for (const [input, expected] of [
['./configure --prefix=/usr/local', '<span class="sh-command">./configure</span>'],
['../configure --enable-gcov', '<span class="sh-command">../configure</span>'],
['./autogen.sh', '<span class="sh-command">./autogen.sh</span>'],
['~/.rubies/ruby-master/bin/ruby -e "puts 1"', '<span class="sh-command">~/.rubies/ruby-master/bin/ruby</span>'],
]) {
assertIncludes(highlight(input), expected, `Failed for: ${input}`);
}
});

test('absolute path commands', () => {
for (const [input, expected] of [
['/bin/sh -c "echo hello"', '<span class="sh-command">/bin/sh</span>'],
['/usr/bin/env ruby', '<span class="sh-command">/usr/bin/env</span>'],
['/opt/homebrew/bin/ruby -v', '<span class="sh-command">/opt/homebrew/bin/ruby</span>'],
]) {
assertIncludes(highlight(input), expected, `Failed for: ${input}`);
}
});

test('environment variables', () => {
for (const [input, expected] of [
['COVERAGE=true make test', ['<span class="sh-envvar">COVERAGE=</span>', '<span class="sh-command">make</span>']],
['CC=clang CXX=clang++ make', ['<span class="sh-envvar">CC=</span>', '<span class="sh-envvar">CXX=</span>', '<span class="sh-command">make</span>']],
['RUBY_TEST_TIMEOUT_SCALE=5 make check', ['<span class="sh-envvar">RUBY_TEST_TIMEOUT_SCALE=</span>', '<span class="sh-command">make</span>']],
]) {
for (const exp of [expected].flat()) {
assertIncludes(highlight(input), exp, `Failed for: ${input}`);
}
}
});

test('hyphens in words not options', () => {
// Hyphen in @herb-tools/linter should NOT be treated as option
let result = highlight('npx @herb-tools/linter');
assertIncludes(result, '<span class="sh-command">npx</span>');
refuteIncludes(result, '<span class="sh-option">-tools/linter</span>');
assertIncludes(result, '@herb-tools/linter');

// Command with hyphen gets highlighted as command, not option
result = highlight('some-command arg');
assertIncludes(result, '<span class="sh-command">some-command</span>');
refuteIncludes(result, '<span class="sh-option">');
});

test('complex commands', () => {
// Typical shell command with prompt
let result = highlight('$ bundle exec rubocop -A');
assertIncludes(result, '<span class="sh-prompt">$</span>');
assertIncludes(result, '<span class="sh-command">bundle</span>');
assertIncludes(result, '<span class="sh-option">-A</span>');

// Complex git command
result = highlight('$ git commit -m "Fix bug" --no-verify');
assertIncludes(result, '<span class="sh-prompt">$</span>');
assertIncludes(result, '<span class="sh-command">git</span>');
assertIncludes(result, '<span class="sh-option">-m</span>');
assertIncludes(result, '<span class="sh-string">&quot;Fix bug&quot;</span>');
assertIncludes(result, '<span class="sh-option">--no-verify</span>');
});

test('multiline with comments', () => {
const code = `# Generate documentation (creates _site directory)
bundle exec rake rdoc

# Force regenerate documentation
bundle exec rake rerdoc
`;

const result = highlight(code);
assertIncludes(result, '<span class="sh-comment"># Generate documentation (creates _site directory)</span>');
assertIncludes(result, '<span class="sh-comment"># Force regenerate documentation</span>');
});

test('empty and whitespace', () => {
assert.equal(highlight(''), '');
assert.equal(highlight(' \n\t \n '), ' \n\t \n ');
});

test('html escaping', () => {
let result = highlight('echo "<script>alert(1)</script>"');
assertIncludes(result, '&lt;script&gt;');
assertIncludes(result, '&lt;/script&gt;');

result = highlight('echo "a && b"');
assertIncludes(result, '&amp;&amp;');
});
Loading
Loading